前几天我对underscore.js的整体结构做了分析,今天我将针对underscore封装的方法进行具体的分析,代码的一些解释都写在了注释里,那么废话不多说进入今天的正文。

没看过上一篇的可以猛戳这里:underscore.js源码解析(一)

underscore.js源码GitHub地址: https://github.com/jashkenas/underscore/blob/master/underscore.js

本文解析的underscore.js版本是1.8.3

_.each

  1. _.each = _.forEach = function(obj, iteratee, context) {
  2. //optimizeCb( )是underscore内部用来执行函数的很重要的方法,这个我们后面再聊
  3. iteratee = optimizeCb(iteratee, context);
  4. var i, length;
  5. if (isArrayLike(obj)) {
  6. //判断是否是类数组,一般不会传入类似 {length: 3} 这样的数据
  7. for (i = 0, length = obj.length; i < length; i++) {
  8. iteratee(obj[i], i, obj);
  9. }
  10. } else {
  11. //对象处理,这个_.keys( )我们也后面再聊
  12. var keys = _.keys(obj);
  13. for (i = 0, length = keys.length; i < length; i++) {
  14. iteratee(obj[keys[i]], keys[i], obj);
  15. }
  16. }
  17. return obj;
  18. };

_.each结构很清晰,如果是数组,就遍历数组调用相应的处理方法,如果是对象的话,就遍历对象调用相应的处理方法。

其中判断是否为类数组的代码如下:

  1. var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  2. //获取"length"属性
  3. var getLength = property('length');
  4. //判断是否是类数组
  5. var isArrayLike = function(collection) {
  6. var length = getLength(collection);
  7. return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  8. };

类数组,即拥有 length 属性并且 length 属性值为 Number 类型的元素,例如数组、arguments、HTMLCollection 以及 NodeList 等等,当然 {length: 3} 这种对象也满足条件,但是_.each一般不会传这种值。

这种判断类数组的方法还是可以学习借鉴一下的。

 
接下来我们来聊上面提到的optimizeCb(),它是underscore内部用来执行函数的很重要的方法,并且改变所执行函数的作用域。

optimizeCb

  1. var optimizeCb = function(func, context, argCount) {
  2. if (context === void 0) return func;
  3. //argCount为函数参数的个数,针对不同参数个数进行不同的处理
  4. switch (argCount == null ? 3 : argCount) {
  5. //为单值的情况,例如times函数
  6. case 1: return function(value) {
  7. return func.call(context, value);
  8. };
  9. //因为2个参数的情况没用被用到,所以在新版中被删除了
  10. //3个参数用于一些迭代器函数,例如map函数
  11. case 3: return function(value, index, collection) {
  12. return func.call(context, value, index, collection);
  13. };
  14. // 4个参数用于reduce和reduceRight函数
  15. case 4: return function(accumulator, value, index, collection) {
  16. return func.call(context, accumulator, value, index, collection);
  17. };
  18. }
  19. return function() {
  20. return func.apply(context, arguments);
  21. };
  22. };

cb和_.iteratee

  1. var cb = function(value, context, argCount) {
  2. //如果为空,则返回value本身(identity函数就是一个返回本身的函数 )
  3. if (value == null) return _.identity;
  4. //如果为函数,则改变所执行函数的作用域
  5. if (_.isFunction(value)) return optimizeCb(value, context, argCount);
  6. //如果是对象,判断是否匹配(matcher是一个用来判断是否匹配的,我们具体后续再聊)
  7. if (_.isObject(value)) return _.matcher(value);
  8. return _.property(value);
  9. };
  10. // 通过调用cb函数,生成每个元素的回调
  11. _.iteratee = function(value, context) {
  12. return cb(value, context, Infinity);
  13. };

_.keys

  1. _.keys = function(obj) {
  2. //如果不是对象,返回空数组
  3. if (!_.isObject(obj)) return [];
  4. //如果支持原生的方法,就调用原生的keys方法
  5. if (nativeKeys) return nativeKeys(obj);
  6. var keys = [];
  7. //记录所有属性名
  8. for (var key in obj) if (_.has(obj, key)) keys.push(key);
  9. // IE9以下枚举bug的兼容处理
  10. if (hasEnumBug) collectNonEnumProps(obj, keys);
  11. return keys;
  12. };
获取所有的属性名存在数组当中。
这里的in操作符不仅在对象本身里查找,还会在原型链中查找。_.keys上增加了_.has()判断,将原型上的过滤。
 
其中IE9以下枚举bug兼容处理源码如下:

  1. //判断是否存在枚举bug
  2. var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
  3. //不可枚举的属性如下
  4. var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
  5.  
  6. var collectNonEnumProps = function(obj, keys) {
  7. var nonEnumIdx = nonEnumerableProps.length;
  8. var constructor = obj.constructor;
  9. var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
  10.  
  11. // Constructor单独处理部分.
  12. var prop = 'constructor';
  13. 如果对象和keys都存在constructor属性,则把他存入keys数组当中
  14. if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
  15.  
  16. while (nonEnumIdx--) {
  17. prop = nonEnumerableProps[nonEnumIdx];
  18. //如果obj对象存在上面数组里那些不可枚举的属性但是不在原型中,并且keys数组里面也没有的话
  19. if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)){
  20. //将其添加进来
  21. keys.push(prop);
  22. }
  23. }
  24. };

既然都说到了keys那么顺带着也介绍一下allkeys吧。

_.allKeys

  1. _.allKeys = function(obj) {
  2. if (!_.isObject(obj)) return [];
  3. var keys = [];
  4. //获取所有的key
  5. for (var key in obj) keys.push(key);
  6. // 依然是IE9以下枚举bug的兼容处理
  7. if (hasEnumBug) collectNonEnumProps(obj, keys);
  8. return keys;
  9. };

其实keys和allKeys代码对比就少了if (_.has(obj, key)),allKeys是获取所有的,包括继承的

在介绍_.matcher之前,需要先介绍一下createAssigner 和_.isMatch。

createAssigner

  1. var createAssigner = function(keysFunc, defaults) {
  2. return function(obj) {
  3. var length = arguments.length;
  4. //判断是否是对象
  5. if (defaults) obj = Object(obj);
  6. //如果一个参数或者对象为空,则返回这个对象
  7. if (length < 2 || obj == null) return obj;
  8. //从第二个参数开始
  9. for (var index = 1; index < length; index++) {
  10. var source = arguments[index],
  11. //获取对应的keys
  12. //keysFunc只有keys和allKeys两种,在下面_.extend和_.extendOwn中可以看到
  13. keys = keysFunc(source),
  14. l = keys.length;
  15. //进行拷贝处理
  16. for (var i = 0; i < l; i++) {
  17. var key = keys[i];
  18. //defaults是为了对defaults做单独处理而添加的参数,具体的解释_.defaults里做详细分析
  19. //在_.extend和_.extendOwn中default没有传值所以是underfinded,所以下面判断条件横为true,正常进行拷贝处理
  20. if (!defaults || obj[key] === void 0) obj[key] = source[key];
  21. }
  22. }
  23. return obj;
  24. };
  25. };
  1. //把后面的source拷贝到第一个对象
  2. _.extend = createAssigner(_.allKeys);
  3.  
  4. //把后面的source拷贝到第一个对象(只拷贝实例的)
  5. // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
  6. _.extendOwn = _.assign = createAssigner(_.keys);
  7.  
  8. //跟_.extend相似,只是当key相同,只会去第一次的键值对,不会被后面的覆盖
  9. _.defaults = createAssigner(_.allKeys, true);
把createAssigner中处理defaults的代码拿到这里具体分析
 
  1. if (!defaults || obj[key] === void 0) obj[key] = source[key];

此时defaults参数为true,所以就要看obj[key] === void 0这句了,void 0返回的就是underfinded,那么这里换句通俗的话说就是判断key之前没有出现过相同的值时,才进行拷贝处理,如果后面出现相同的key,将不再进行拷贝操作,只保存第一次的键值对结果。

_.isMatch

  1. //用来判断该属性是否在对象中 (包括原型链)
  2. _.isMatch = function(object, attrs) {
  3. var keys = _.keys(attrs), length = keys.length;
  4. //判断对象是否为空
  5. if (object == null) return !length;
  6. //判断是否是对象
  7. var obj = Object(object);
  8. for (var i = 0; i < length; i++) {
  9. var key = keys[i];
  10. //如果两者值不等或者不在属性不在对象当中则返回false
  11. if (attrs[key] !== obj[key] || !(key in obj)) return false;
  12. }
  13. return true;
  14. };

_.matcher和_.matches

  1. // 判断对象是否匹配attrs的属性
  2. _.matcher = _.matches = function(attrs) {
  3. //进行拷贝
  4. attrs = _.extendOwn({}, attrs);
  5. return function(obj) {
  6. //用来判断该属性是否在对象中,上文有提及
  7. return _.isMatch(obj, attrs);
  8. };
  9. };

小结

今天介绍了underscore.js中部分封装函数,其他的会在后面的文章继续一一分析

感谢大家的观看,也希望能够和大家互相交流学习,有什么分析的不对的地方欢迎大家批评指出

参考资料

https://segmentfault.com/a/1190000000531871
http://www.w3cfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370

underscore.js源码解析(二)的更多相关文章

  1. underscore.js源码解析(五)—— 完结篇

    最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题. 没看过前几篇的可以猛戳这里: underscore.js源码解析( ...

  2. underscore.js源码解析(四)

    没看过前几篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二) underscore.js源码解析(三) underscore.js源码GitHub地 ...

  3. underscore.js源码解析(三)

    最近工作比较忙,做不到每周两篇了,周末赶着写吧,上篇我针对一些方法进行了分析,今天继续. 没看过前两篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二 ...

  4. underscore.js源码解析(一)

    一直想针对一个框架的源码好好的学习一下编程思想和技巧,提高一下自己的水平,但是看过一些框架的源码,都感觉看的莫名其妙,看不太懂,最后找到这个underscore.js由于这个比较简短,一千多行,而且读 ...

  5. underscore.js源码解析【'_'对象定义及内部函数】

    (function() { // Baseline setup // -------------- // Establish the root object, `window` (`self`) in ...

  6. underscore.js源码解析【对象】

    // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in . ...

  7. underscore.js源码解析【函数】

    // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a ...

  8. underscore.js源码解析【数组】

    // Array Functions // --------------- // Get the first element of an array. Passing **n** will retur ...

  9. underscore.js源码解析【集合】

    // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `f ...

随机推荐

  1. MySQL->AUTO_INCREMENT[20180516]

    MySQL表格中自增长主键AUTO_INCREMENT使用,实现序列的最简单的方式   创建一个AUTO_INCREMENT自增的表 mysql> create table seq_test( ...

  2. Spring Bean d的作用域

    在spring中,可以在<bean>元素的scop属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的. 默认情况下,spring只为每个在IOC容器里声明的bean创建唯 ...

  3. jQuery实现全选、不选和反选功能

    jQuery结合Font Awesome字体图标实现全选.不选和反选功能 Font Awesome字体图标链接地址:http://www.fontawesome.com.cn/faicons/ 效果: ...

  4. laravel5.5源码笔记(三、门面类facade)

    上次说了provider,那么这次来说说facade 首先是启动的源头,从laravel的kernel类中的$bootstrappers 数组,我们可以看到它的一些系统引导方法,其中的Register ...

  5. STM32 HAL库学习系列第2篇 GPIO配置

    GPIO 库函数 基本就是使用以下几个函数 GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void H ...

  6. MongoDB成为最受开发人员期待的数据库系统

    本文翻译之MongoDB官方博客,原文地址:https://www.mongodb.com/blog/post/stack-overflow-research-developers-mongodb-m ...

  7. SQL学习笔记:函数

    SQL函数 AVG select AVG(col) AS avgvalue from tablename select col2 from tablename where col1>(selec ...

  8. Oracle入门第三天(下)——子查询

    一.子查询 1.子查询语法 SELECT select_list FROM table WHERE expr operator (SELECT select_list FROM table) 示例: ...

  9. Java基础——枚举

    一.使用枚举类之前是如何实现枚举的  在JDK1.5之前,我们定义常量都是:public static fianl....:定义枚举也可以通过如下的方式: package com.jiangbei.t ...

  10. day3 直方图

    1.绘制直方图 # coding=utf-8 import cv2 import numpy as np from matplotlib import pyplot as plt img1 = cv2 ...