版本 Underscore.js 1.9.1
一共 1693 行,一行行看吧,希望作者是正序写的……
注释我就删了…… 太长了……
整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行函数)。
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {};
获取当前运行环境根对象。
在浏览器中为 self(=window) 在服务端中是 global 在一些虚拟机中是 this
var previousUnderscore = root._;
如果环境中已经定义了同名变量,防止对其造成覆盖,先把这个变量缓存起来。
var ArrayProto = Array.prototype, ObjProto = Object.prototype; var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeCreate = Object.create;
定义一些变量来存储 JS 定义的对象原型和方法,以便后续使用。
var Ctor = function(){};
根据注释,这个裸函数是用来代理原型交换的?英文不好……后面应该可以看到用处,不急。
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
_ 是一个构造函数,传入的对象如果已经是 _ 实例就直接返回
我们知道当我们通过 new foo() 创建对象时会创建一个新的对象,然后将它的原型链绑定为 foo.propotype ,然后把这个对象作为 foo 调用的this,如果 foo 没有返回值的话,就返回这个对象。
所以通过 this instanceof _ 可以判断是否是构造调用(是否加 new)如果不是的话 就手动加一个 new 调用一次。
通过foo生成一个对象,他有一个属性 _wrapped 的值是传入的obj。
if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }
因为 node 环境中会有 exports 变量,由此判断是在浏览器还是服务端。服务端的话就导出 _ ,否则在根元素上添加 _ 变量。
_.VERSION = '1.9.1';
版本号
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); }; // The 2-argument case is omitted because we’re not using it. 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); }; };
其实这个函数可以简化成
var optimizeCb = function(func, context) { return function() { return func.apply(context, arguments); }; };
所以说这就相当于实现了一个 bind 。 optimizeCb(func, context) = func.bind(context)
那为什么要分那么多情况呢?因为 apply 比 call 慢,而且某些情况下,还会慢很多。
至于 void 0 是什么,void + 表达式会返回 undefined 这个算常识吧。而不使用 undefined 因为在某些老的浏览器中 undefined 可以被赋值,出于兼容性考虑。
var builtinIteratee; var cb = function(value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); if (value == null) return _.identity; // _.identity 函数: value => value if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); return _.property(value); }; _.iteratee = builtinIteratee = function(value, context) { return cb(value, context, Infinity); };
这个看到有点懵比……
首先 builtinIteratee 就是一个用来判断 iteratee 是否被用户改变的临时变量,没有其他用处。
_.iteratee() 是一个函数 默认返回 cb
cb 作为操作集合的函数的回调函数使用
如果 _.iteratee 被修改就调用修改后的函数
如果 value == null 就返回 _.identity 一个传入什么就返回什么的函数
如果 value 是函数 就返回 optimizeCb(value, context, argCount) 也就是 value.bind(context)
如果 value 是对象 且不是数组 就返回 _.matcher(value) 下面说
以上都不符合就返回 _.property(value); 后面看到再说吧
_.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; }; _.matcher = _.matches = function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // e.g. var isZhangsan = _.matcher({ firstname: 'san', lastname: 'zhang' }); console.log(isZhangsan({ firstname: 'san', lastname: 'zhang', age: 55 })); // true console.log(isZhangsan({ firstname: 'si', lastname: 'zhang' })); // false
好了 现在知道不是正序写的了 哭唧唧 先不看 _.extendOwn 是什么鬼东西了 反正看名字肯定是一个扩展对象的函数
首先看 isMatch , keys 相当于 Object.keys , isMatch 就是判断 attrs 中的 key 是否在 object 中都存在且对应的值都相等。
那么 _.matcher 就是设定 attrs 返回函数。返回的函数传入 obj 看其是否符合 attrs。
var restArguments = function(func, startIndex) { startIndex = startIndex == null ? func.length - 1 : +startIndex; return function() { var length = Math.max(arguments.length - startIndex, 0), rest = Array(length), index = 0; for (; index < length; index++) { rest[index] = arguments[index + startIndex]; } switch (startIndex) { case 0: return func.call(this, rest); case 1: return func.call(this, arguments[0], rest); case 2: return func.call(this, arguments[0], arguments[1], rest); } var args = Array(startIndex + 1); for (index = 0; index < startIndex; index++) { args[index] = arguments[index]; } args[startIndex] = rest; return func.apply(this, args); }; };
相当于 ES6 的剩余参数,从 startIndex 开始的所有参数当做一个数组传入。分情况使用 call 还是上面提到的效率问题。
使用举例:
function sum(arr) { return arr.reduce((previous, current) => { return previous + current; }); } var restArgumentsWrapperSum = restArguments(sum); console.log(restArgumentsWrapperSum(1, 2, 3));
// var nativeCreate = Object.create; 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; };
相当于手动实现了一个 Object.create 利用了上面不知道什么用空函数 Ctor 。
new Ctor 没有加括号,在构造调用的时候,如果不传入参数,可以不加括号。相当于 new Ctor() 。
Object.create(foo) 就是创建一个对象 对象的 [[Prototype]] 为 foo.prototype,这里通过 new 实现。结束之后再将 Ctor 的 protottype 赋值为 null 。
var shallowProperty = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // e.g. var getId = shallowProperty('id'); let obj = { id: 233, otherKey: 'who care' }; console.log(getId(obj)); // 233
传入一个 key 生成一个 获取对象属性 key 的值 的函数。
// var hasOwnProperty = ObjProto.hasOwnProperty; var has = function(obj, path) { return obj != null && hasOwnProperty.call(obj, path); }
就是使用了 Object.prototype.hasOwnProperty 判断对象 obj 是否存在属性 path
var deepGet = function(obj, path) { var length = path.length; for (var i = 0; i < length; i++) { if (obj == null) return void 0; obj = obj[path[i]]; } return length ? obj : void 0; }; // e.g. var obj = { user: { name: { first: 'san', last: 'zhang', }, id: 3 } } console.log(deepGet(obj, ['user', 'name', 'last'])); // zhang
根据路径获取对象指定嵌套属性的值。
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = shallowProperty('length'); var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; };
如果一个对象有属性 length ,属性值为数字且在 [0, 2^53-1] 之间,则判断这个对象为类数组。
类数组常见的有 arguments、HTML Collection,数组也是类数组。
到这里是 174 行,下面就是集合相关函数了,明天再看 =。=