Underscore 源码阅读

"阅读 underscore源码, 提高 JS 水平"

Posted by 刘一凡 on March 26, 2016

经常听别人说 underscore 代码写得优雅, 适合阅读, 近几天抽时结合别人的分析, 自己也看一下. 我看的版本是 1.8.2 underscore 带注释源码

这是我看的比较好的一篇分析的博文
underscore源码经典(一)
underscore源码经典(二)
underscore源码经典(三)
underscore源码经典(完)

闭包


underscore 代码包在一个自执行的匿名函数中

(function(){
    
    // ...

}.call(this))

基本设置


对象原型

保存对象原型, 以便下面的调用

// 创建一个全局对象, 在浏览器中表示 window, 在服务器中表示 export
var root = this;

// 保存"_"(下划线变量)被覆盖之前的值
// 如果出现命名冲突或考虑到规范, 可通过_.noConflict()方法恢复"_"被Underscore占用之前的值, 并返回Underscore对象以便重新命名
var previousUnderscore = root._;

// 保存对象原型
var ArrayProto = Array.prototype,
    ObjProto   = Object.prototype,
    FuncProto  = Object.prototype;

常用方法

保存常用方法, 以便快速引用

// 保存常用方法, 以便快速引用
var push            = ArrayProto.push,
    slice           = ArrayProto.slice,
    toString        = ObjProto.toString,
    hasOwnPrototype = ObjProto.hasOwnPrototype;

保存 ES5 原生方法

ES5 实现的原生方法保存, 以便浏览器支持原生方法时, 使用原生方法

// 保存 ES5 实现的原生方法
var nativeIsArray = Array.isArray,
    nativeKeys    = Object.keys,
    nativeBind    = FuncProto.bind,
    nativeCreate  = Object.create;

创建一个 Underscore 对象

Underscore 对象, 用 _ 表示

// 一个全局引用函数
var Ctor = function(){};

// Underscore 对象以便下面调用

var _ = function(obj){
  if(obj instanceof _) return obj
  if(!(this instanceof _)) return new _(obj)
  this._wraped = obj;
}

命名空间

针对不同的浏览器环境, 不同的命名空间

// 针对不同的环境, 将 underscore 的命名空间存放到不同的对象
if(typeof exports !== "undefined"){   // node.js 环境
  if(typeof module !== "undefined" && module.exports){
    exports = module.exports = _;
  }
  exports._ = _;
} else { // 浏览器环境
  root._ = _;
}

版本号

记录版本号

_.VERSION = "1.8.2"

内部函数, 改变函数作用域

这是一个比较重要的内部函数, 主要来执行函数改变所执行函数的作用域
argCount 参数来指定参数的个数,并对参数个数小于等于 4 的情况做了处理

var optimizeCb = function(func, context, argCount){
  if(context === void 0) return func;
  switch(argCount == null ? 3 : argCount){
    case 1 : return function(value){
      return func.call(context, value)
    };
    case 2 : return function(value, other){
      return func.call(context, value, other)
    };
    case 3 : return function(value, index, collection){
      return func.call(context, value, index, collection)
    };
    case 4 : return function(accumulator, value, index, collection){
      return func.call(context, accumulator, value, index, collection)
    };
  }

  return function(){
    return func.apply(context, arguments)
  }

switch 这里不太理解, 看了别人的分析, 这段 switch 完全是多余的, 可以用下面这段代替
switch 那段的唯一用处, 就是可以向你展示一个规范, 告诉你在传递几个参数的时候, 相应的参数什么是什么东东而已, 而这种东东, 完全可以写在注释里.

var optimizeCb = function(func, context) {
    if (context === void 0) return func;
    return function() {
      return func.apply(context, arguments);
    };
};

这也是一个比较常用的内部函数,只是对参数进行了判断:
如果是函数则返回上面说到的回调函数;
如果是对象则返回一个能判断对象是否相等的函数;
默认返回一个获取对象属性的函数。

  var cb = function(value, context, argCount){
    // value 是否为空, 如果是空, 返回本身
    if(value == null) return _.identity;
    // value 为函数时, 改变作用域
    if(_.isFunction(value)) return optimizeCb(value, context, argCount)
    // value 为对象时, 判断是否匹配
    if(_.isObject(value)) return _.matcher(value)
    return _.property(value)
  }
  _.iteratee = function(value, context){
    return cb(value, context, Infinity)
  }

// 这里还没搞懂
var createAssigner = function(keysFunc, undefinedOnly){
  return function(obj){
    // 参数的数量
    var length = arguments.length;
    console.log(length)
    // 如果对象为 null, 或者只有一个参数, 返回这个对象
    if(length < 2 || obj == null) return obj;
    // 从第二个对象开始循环
    for(var index = 1; index < length; index ++){
      var source = arguments[index],
          keys   = keysFunc(source),
          l      = keys.length;
      for(var i = 0; i < l; i++){
        var key = keys[i];
        if(!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
      }
    }
  }

  return obj
}

创建一个继承自其他对象的对象

创建一个继承自其他对象的对象

var baseCreate = function(prototype){
  if(!_isObject(prototype)) return {}
  if(nativeCreate) return nativeCreate(prototype)
  Ctor.prototype = prototype
  var result = new Ctor
  Ctor.prototype = null
  return result
}

JS 最大精确整数

JS能够处理的最大精确整数

var MAX_ARRAR_INDEX = Math.pow(2, 53) - 1;
var isArrayLike = function(collection){
  var length = collection && collecion.length;
  // 如果集合有 length 属性且为数字并且大于0小于最大精确整数, 则判断为类数组
  return typeof length == "number" && length >= 0 && length <= MAX_ARRAR_INDEX;
}  

数据判断

判断是否为对象

_.isObject = function(obj){
  var type = typeof obj;
  return type === "function" || type === "object" && !!obj
}

判断是否为函数

_.isFunction = function(obj) {
      return typeof obj == 'function' || false;
};

集合方法


each(forEach)

_.each(list, literator, [context]) 别名 forEach
对一个 list 的所有元素进行迭代,对每一个元素执行 iterator 函数。iteratorcontext 对象绑定,如果传了这个参数。
每次 iterator 的调用将会带有三个参数:(element, index, list)
如果 list 是一个 JavaScript 对象,iterator 的参数将会是 (value, key, list)。如果有原生的 forEach 函数就会用原生的代替。

这里用到了几个方法
optimizeCb()
iteratee()
枚举 _.(keys)

// 迭代 each
_.each = _.forEach = function(obj, iteratee, context){
  iteratee = optimizeCb(iteratee, context)
  var i, length;
  if(isArrayLike(obj)){      // 当传入的 obj 是 数组
    for(i = 0, length = obj.length; i < length; i++){
      iteratee(obj[i], i, obj)
    }
  } else {          // 当传入的 obj 是 对象
    var keys = _.keys(obj);
    for(i = 0, length = keys.length; i < length; i++){
      iteratee(obj[keys[i]], keys[i], obj)
    }
  }

  return obj;
}

示例 1 : 数组

var a = [1, 2, 3];
_.each(a, function(value, index, list){
  console.log("value:" + value + ", " + "index:" + index + ", " + "list:" + list)
})

=> 输出 value:1, index:0, list:1,2,3
=> 输出 value:2, index:1, list:1,2,3
=> 输出 value:3, index:2, list:1,2,3

示例 2 : 对象

var b = {"前端" : "张三", "后端" : "李四"}
_.each(b, function(value, index, list){
  console.log("value:" + value + ", " + "index:" + index + ", " + "list:" + list)
})

=> value:张三, index:前端, list:[object Object]
=> value:李四, index:后端, list:[object Object]

map(collect)

_.map(list, literator, [context]) 别名 collect
list 中每个元素运行 literator 函数, 并返回执行过后的新生成的数组
each 方法一样, 每次 iterator 的调用将会带有三个参数:(element, index, list)

_.map = _.collect = function(obj, iteratee, context){
  iteratee    = cb(iteratee, context)
  var keys    = !isArrayLike(obj) && _.keys(obj),
      length  = (keys || obj).length,
      results = Array(length);
  for(var index = 0; index < length; index++){
    var currentKey = keys ? keys[index] : index;
    results[index] = iteratee(obj[currentKey], currentKey, obj)
  }

  return results
}

示例 1 : 数组

var a = [1, 2];
var b = _.map(a, function(value, key, list){
  return value + 1
})
console.log(b)

=> 输出 [2, 3]

示例 2 : 对象

var a = {"月薪" : 1000};
var b = _.map(a, function(value, key, list){
  return value + 200;
})
// 实际 b 返回的是数组 [1200]
console.log("当前月薪为 " + b)

=> 当前月薪为 1200

reduce, reduceRight

_.reduce(list, iteratee, [memo], [context]) 别名 inject, foldl
reduce 方法把 list 中元素归结为一个单独的数值
memoreduce 函数的初始值, reduce 的每一步都需要由 iteratee 返回
iteratee 迭代传递 4 个参数, memo, value, index(key), list

// 创建一个向左, 向右的 reduce 函数
    function createReduce(dir){
      function iterator(obj, iteratee, memo, keys, index, length){
        for(; index >=0 && index < length; index += dir){
          var currentKey = keys ? keys[index] : index;
          memo = iteratee(memo, obj[currentKey], currentKey, obj);
        }

        return memo;
      }

      return function(obj, iteratee, memo, context){
        iteratee   = optimizeCb(iteratee, context, 4);
        var keys   = !isArrayLike(obj) && _.keys(),
            length = (keys || obj).length,
            index  = dir > 0 ? 0 : length - 1;

        // 没有传递初始值, 将第一个元素将取代传递给列表中下一个元素调用 iteratee的 memo 参数
        if(arguments.length < 3){
          memo = obj[keys ? keys[index] : index];
          index += dir;
        }

        return iterator(obj, iteratee, memo, keys, index, length)
      }
    }

    // 从开始左侧迭代 reduce
    _.reduce = _.foldl = _.inject = createReduce(1);

    // 从右侧右侧迭代 reduce
    _.reduceRight = _.foldr = createReduce(-1);

示例 1 : reduce

var a = [1, 2, 3];
var sum = _.reduce(a, function(memo, num){
  return memo + num
}, 0)
console.log(sum)

=> 输出 6

示例 2 : reduceRight

var list = [[0, 1], [2, 3], [4, 5]];
var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
console.log(flat)

=> [4, 5, 2, 3, 0, 1]

find

_.find(list, predicate, [context]) 别名: detect
遍历 list, 返回第一个通过 predicate 函数的元素值, 没有通过返回 undefined
如果找到符合判断的值, 立即返回, 不会遍历整个 list
predicate 函数包含 3 个属性, value, index, list

这里用到了几个方法
createIndexFinder()
_.findIndex()

_.find = _.detect = function(obj, predicate, context){
  var key;
  if (isArrayLike(obj)) {
    key = _.findIndex(obj, predicate, context);
  } else {
    key = _.findKey(obj, predicate, context);
  }
  if (key !== void 0 && key !== -1) return obj[key];
};

示例

var a = [1, 2, 3, 4];
var b = _.find(a, function(num){
  return num % 2 == 0
})
console.log(b)

=> 2

filter

_.filter(list, predicate, [context]) 别名 select
遍历 list 的每个值, 返回一个包含所有通过 predicate 函数的元素值的集合
predicate 函数包含 3 个属性, value, index, list

_.filter = _.select = function(obj, predicate, context){
  var results = [];
  predicate = cb(predicate, context);
  _.each(obj, function(value, index, list){
    if(predicate(value, index, list)) results.push(value)
  })

  return results;
}

示例

var a = [1, 2, 3, 4];
var b = _.filter(a, function(num){
  return num % 2 == 0
})
console.log(b)

=> [2, 4]

reject

_.reject(list, predicate, [context])
返回 list 中没有通过 predicate 函数检测的元素集合, 与 filter 相反

_.reject = function(obj, predicate, context){
  return _.filter(obj, _.negate(cb(predicate)), context)
}

示例

var a = [1, 2, 3, 4];
var b = _.reject(a, function(num){
  return num % 2 == 0
})
console.log(b)

=> [1, 3]

数组方法


获取 Index 方法

创建一个可以从左、从右获取 index 值的方法

function createIndexFinder(dir){
  return function(array, predicate, context){
    predicate = cb(predicate, context);
    var length = array != null && array.length;
    var index = dir > 0 ? 0 length - 1;
    for(;index >=0 && index < length; index += dir){
      // 获取符合条件的 key, 返回 index
      if(predicate(array[index], index, array)) return index;
    }

    return -1;
  }
}
// 从左开始
_.findIndex = createIndexFinder(1);

// 从右开始
_.findLastIndex = createIndexFinder(-1);

对象方法


枚举

返回对象的可枚举属性和方法的名称, 返回一个数组
这是用到的了 _.has(), 因为 for ... in ... 可获取到继承自原型的属性

// IE9下面枚举 BUG 处理

/**
*  propertyIsEnumerable()是用来检测属性是否属于某个对象的,如果检测到了,返回true,否则返回false.
*  1.这个属性必须属于实例的,并且不属于原型.
*  2.这个属性必须是可枚举的,也就是自定义的属性,可以通过for..in循环出来的.
*  只要符合上面两个要求,就会返回true;
*/
var hasEnumBug = !{toString : null}.propertyIsEnumerable("toString")
console.log(hasEnumBug)
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
                'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

function collectNonEnumProps(obj, keys) {
  var nonEnumIdx = nonEnumerableProps.length;
  var constructor = obj.constructor;
  var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

  var prop = 'constructor';
  if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

  while (nonEnumIdx--) {
    prop = nonEnumerableProps[nonEnumIdx];
    if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
      keys.push(prop);
    }
  }
}   

// 返回对象的可枚举属性和方法的名称, 返回一个数组
_.keys = function(obj){
  if(!_.isObject(obj)) return []
  if(nativeKeys) return nativeKeys(obj)
  var keys = []
  // 遍历 obj 中的属性
  for(var key in obj)
    // 找到 obj中的可枚举的属性, 放到 keys 中
    if(_.has(obj, key)) keys.push(key)

  // 处理IE9下面的不能通过 for key in ... 遍历
  if(hasEnumBug) collectNonEnumProps(obj, keys);
  return keys;
}

has

判断属性是否是对象的实例属性, 换句话说, 不是来自 prototype

// 判断属性是否是对象的实例属性, 换句话说, 不是来自 prototype
_.has = function(obj, key){
  return obj != null && hasOwnProperty.call(obj, key)
}

函数方法


negate

返回一个新的 predicate 函数的否定版本

_.negate = function(predicate){
  return function(){
    return !predicate.apply(this, arguments)
  }
}

compose

_.compose(*functions)
返回函数集 functions 组合后的复合函数, 也就是一个函数执行完再把函数执行结果作为参数传递给下个函数执行

_.compose = function(){
  var args = arguments;
  // 最后一个参数的 index 值
  var start = args.length - 1;
  return function(){
    var i = start;  
    var result = args[start].apply(this, arguments)
    while (i--) result = args[i].call(this, result)
    return result;
  }

示例

var personName = function(name){
  return name;
};
var personal = function(str){
  return "我叫" + str + "!";
}
var introduction = _.compose(personal, personName);
console.log(introduction("刘一凡"))

=> 我叫刘一凡!

after

_.after(count, function)
创建一个函数, 运行 count 次后, 触发 function

_.after = function(times, func){
  return function(){
     if(--times < 1){
       return func.apply(this, arguments)
     }
  }
}

示例

function a(){
  console.log("a")
}

var ConsoleA = _.after(3, a);

ConsoleA();  // 没触发 a()
ConsoleA();  // 没触发 a()
ConsoleA();  // 触发 a(), 输出 a

before

_.before(count, function)
创建一个函数, 调用 function 不超过 count 次, 第 count 次返回, 不执行 function

_.before = function(times, func){
  var memo ;
  return function(){
    if(--times > 0){
      memo = func.call(this, arguments)
    }
    if(times <= 1) func = null;

    return memo;
  }
}

示例

function a(){
  console.log("a")
}

var ConsoleA = _.before(3, a);

ConsoleA();  // 触发 a()
ConsoleA();  // 触发 a()
ConsoleA();  // 没触发 a()

公用函数


identity

返回本身

_.identity = function(value){
  return value;
}