这次源码分析为 1.8.3 version
之前曾读过一次,但是没有作下笔记。这次从新阅读特制此笔记javascript
underscore是包裹在一个闭包内部的防止污染全局变量java
(function(){ }())
若是有人不知道的话我能够说一下
这样匿名函数当即调用
由于函数划分做用域
在建立做用域以后没有引用,则不污染全局node
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {};
显然判断的是运行的环境。由于多是node环境多是浏览器环境等。git
var previousUnderscore = root._;
若是以前存在_的话,则将其进行保存而不是简单的替换github
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;
保存常见方法的引用。由于频繁的查找。会损耗性能(高性能javascript)数组
var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeCreate = Object.create;
保存es5方法引用浏览器
var Ctor = function(){};
Constructor的缩写,在以后用于对象建立安全
var _ = function (obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
建立一个underscore的对象引用,保证不重复引用
且当用_作函数调用时候,会自动返回_对象
当_调用时候则不会新生_对象闭包
if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }
将_underscore对象挂载到合适的位置。
相似umd的兼容写法
_.VERSION = '1.8.3';
记录版本号
var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount) { case 1: return function(value) { return func.call(context, value); }; // The 2-parameter case has been omitted only because no current consumers // made use of it. case null: 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); }; };
优化回调(特指函数中传入的回调)
void 0是一个真正的undefined
由于undefined是能够被赋值的
防止undefined值被篡改
接下来就是保证回调函数的执行上下文。
若是回调函数参数只有1个,那么咱们在迭代过程当中咱们只须要值
两个回调函数参数的时候基本不存在这里省略
3个的状况相似forEach那么传入回调函数值(值,索引,被迭代集合对象)
4个的状况则是累加器,相似reduce(累加器,值,索引,被迭代集合对象)
argCount不存在则直接返回一个绑定上下文的回调函数
var builtinIteratee;
一个默认的迭代器
var cb = function (value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); };
为迭代过程当中的元素生产一个回调函数, 该回调函数可以应用到集合中的每一个元素
第一个是否使用默认的迭代器
第二个 若是value为null 则回调只是一个返回自身的函数
第三个 若是value是一个回调函数,则须要optimizeCb回炉改造回调进行优化
第四个 若是value是对象,则返回一个matcher进行对象匹配
最后 若是value只是一个字面量 则将value看作属性名称,默认返回一个获取对象属性的函数
简单的能够认为cb函数是对一个大的范围进行处理 optimizeCb 则是对一个更具体的回调函数进行优化
_.iteratee = builtinIteratee = function (value, context) { return cb(value, context, Infinity); };
underscore内置的迭代回调
Infinity是一个全局的数值,表明无穷大
这又得牵扯到javascript的安全数值了
var restArgs = function (func, startIndex) { startIndex = startIndex == null ? func.length - 1 : +startIndex; //rest判断有没有,没有的话默认是参数的最后。有的话则是有的状况。(+隐式转换数字) return function () { var length = Math.max(arguments.length - startIndex, 0); //防止出现负数 var rest = Array(length); //开辟数组来存储rest for (var index = 0; index < length; index++) { rest[index] = arguments[index + startIndex]; } //例如 func(a,b,...args) //其实是在结构一下 func(1,2,3,4,5,6,7)变成func(1,2,[3,4,5,6,7]) 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); } //根据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的...实现
传入参数 func 须要rest参数的数组 startIndex从哪里开始标识rest参数, 若是不传递, 默认最后一个参数为rest参数
最后是返回一个具有了args(rest)参数的函数
var createAssigner = function(keysFunc, undefinedOnly) { return function(obj) { var length = arguments.length; //得到参数长度 //若是小于2或者传入obj为null 则直接返回 if (length < 2 || obj == null) return obj; //枚举后面的参数对象 for (var index = 1; index < length; index++) { //一个迭代 //传入的keysFunc实际上也是一个函数 //此时的source是对象参数 var source = arguments[index], keys = keysFunc(source), l = keys.length; //遍历对象键 for (var i = 0; i < l; i++) { var key = keys[i]; //若是没有传入undefinedOnly //则必定会执行键值对赋值。键值相同会直接进行覆盖 //若是有undefinedOnly值,则对象赋值则不会进行覆盖 if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; } } //返回处理后的obj对象 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; };
'类'的继承。(即Object.create)而且保证挂载在原型链的属性不会影响所继承的prototype
判断是否为对象,不是就直接返回了。
若是有Object.create则使用Object.create
没有的话下面至关因而一个Object.create的polyfill
var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; };
利用函数式编程思想
这是明显的函数向右柯里化
先传入key返回一个匿名函数为obj
当调用Property(length)的时候。里面则是为obj == null ? void 0 : obj[length];
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; };
得到对象的深层属性
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
最大安全'值'
var getLength = property('length');
像上面说的
var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; };
判断是否是类数组也就是简单判断是否是有length存在而且大于等于0且小于或等于最大安全整数