前言
Underscore.js是一款精简但是对很多常用功能进行了封装的JavaScript框架(英文文档:http://underscorejs.org/,中文文档:http://www.css88.com/doc/underscore/),整个篇幅比较短,所以我觉得很适合我这种前端菜鸟来阅读吧。希望我能坚持阅读下去,并致力于弄懂。
一览
可以看到外面是直接调用匿名匿名函数,相当于function noName{...};noName();
因为它会在里面定义到一些变量,而我们一般是用它内部定义的方法去访问,所以就会存在一个闭包。
(function() {
// 内容
}())
满满的下缀_
可以看到代码中""这个符号出现的频率非常之高,可以查看中文文档,然后会发现它里面的方法前缀都是""来标记的。其实underscore这个名字的意思也是"_"。
预处理篇
定义根
下面我们来看代码,第一句是定义了root变量。root变量就是某个环境夏的最顶端变量,也是原型链的自顶端。在浏览器中是window,在node环境中是global,所以这里有四种判断情况。
联想提示:知道shim和polyfill的区别吗?
是为了能在低ES版本下实现高ES版本所以写的一系列代码,其实shim和polyfill都是这个意思,但是它们的区别是polyfill是特质浏览器中的shim,而shim则支持多个平台,会让我想到这个是因为这个定义也看得出underscore不仅支持浏览器,还支持Node环境。
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
Q:self
是什么东西呀?
A:在浏览器中测试可知,这个self其实就是window。而self.self指向的也同样是window。
Q:为什么使用self.self和global.global能判断它就是window或者global呢?
保存会用到的变量方法
//保存原来的下划线变量意味
var previousUnderscore = root._;
// 数组原型、对象原型、Symbol原型(如果支持Symbol)
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
// 保存数组的push、slice、toString方法,保存Object的hasOwnProperty方法
var push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// 在ES5中会使用到的方法:判断是否为数组,返回键值组成的数组,创建一个对象
var nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeCreate = Object.create;
扩展回忆:
使用Object.create(原型),如果填入的参数为null,就可以创建一个没有原型的对象。
处理参数和函数
var Ctor = function(){};
定义一个空壳函数Ctor来方便做原型的替代
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
定义我们自己的_
变量,是一个可以传入一个obj的函数。如果这个obj是的实例,就返回这个obj。如果不是的话就new一个(obj)返回,new操作会把新对象的原型指向_,this会指向新对象,并且会执行this.wrapped = obj
。所以执行这个函数的话,会得到一个类似于下图的结构:
所以这个对象就成为了_的一个实例。
下面是对于在node环境中的处理此处还没找到实验环境,先跳过:
if (typeof exports != 'undefined' && !exports.nodeType) {
if (typeof module != 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
指明Undersocre.js的版本,这里可以看到我下载的源码是1.9.0版本的。
_.VERSION = '1.9.0';
处理参数
func:函数
context:上下文
argCount:参数个数
这里直接看其实也感觉很难理解,所以我会先在后面提取一个例子,结合文档的用法来搞清楚这个函数到底的作用。
var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
//如果没有传入argCount,进入3,否则对应进入不同的参数
switch (argCount == null ? 3 : argCount) {
// 返回一个直接传入value的函数
case 1: return function(value) {
return func.call(context, value);
};
// 两个参数的情况省略了,因为我们不会用到
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);
};
};
-
context === void 0
是什么意思呀?
这是在stackOverflow上找到的一个回答,void 0可以和undefined有同样的作用,但是它的字节要少一些所以用了它来减少尺寸哈哈哈。 这里使用了argCount == null,argCount如果没有传入实际上是undefined,但是undefined == null,所以没有传入argCount会直接进行3的分支。
一个内置的函数来生成能调用多次调用回调函数:
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) && !_.isArray(value)) return _.matcher(value);
return _.property(value);
};
_.iteratee = builtinIteratee = function(value, context) {
return cb(value, context, Infinity);
};
处理剩余传入的多个参数,化为一个数组,类似于ES6中的...rest:
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);
};
};
创建一个对象
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;
};
返回对象的属性值(浅返回)
var shallowProperty = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key];
};
};
按照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;
};
帮助集合来定义它是否为一个集合。应该以数组/对象的方式来循环吗?
//数组索引最大值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
//获取length值
var getLength = shallowProperty('length');
//是不是类数组?
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};