前言
Underscore.js是一款精簡(jiǎn)但是對(duì)很多常用功能進(jìn)行了封裝的JavaScript框架(英文文檔:http://underscorejs.org/肺樟,中文文檔:http://www.css88.com/doc/underscore/),整個(gè)篇幅比較短新症,所以我覺得很適合我這種前端菜鳥來閱讀吧择示。希望我能堅(jiān)持閱讀下去,并致力于弄懂。
一覽
可以看到外面是直接調(diào)用匿名匿名函數(shù)榛丢,相當(dāng)于function noName{...};noName();
因?yàn)樗鼤?huì)在里面定義到一些變量,而我們一般是用它內(nèi)部定義的方法去訪問挺庞,所以就會(huì)存在一個(gè)閉包晰赞。
(function() {
// 內(nèi)容
}())
滿滿的下綴_
可以看到代碼中""這個(gè)符號(hào)出現(xiàn)的頻率非常之高,可以查看中文文檔选侨,然后會(huì)發(fā)現(xiàn)它里面的方法前綴都是""來標(biāo)記的掖鱼。其實(shí)underscore這個(gè)名字的意思也是"_"。
預(yù)處理篇
定義根
下面我們來看代碼援制,第一句是定義了root變量戏挡。root變量就是某個(gè)環(huán)境夏的最頂端變量,也是原型鏈的自頂端隘谣。在瀏覽器中是window增拥,在node環(huán)境中是global,所以這里有四種判斷情況寻歧。
聯(lián)想提示:知道shim和polyfill的區(qū)別嗎?
是為了能在低ES版本下實(shí)現(xiàn)高ES版本所以寫的一系列代碼掌栅,其實(shí)shim和polyfill都是這個(gè)意思,但是它們的區(qū)別是polyfill是特質(zhì)瀏覽器中的shim码泛,而shim則支持多個(gè)平臺(tái)猾封,會(huì)讓我想到這個(gè)是因?yàn)檫@個(gè)定義也看得出underscore不僅支持瀏覽器,還支持Node環(huán)境噪珊。
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
Q:self
是什么東西呀晌缘?
A:在瀏覽器中測(cè)試可知,這個(gè)self其實(shí)就是window痢站。而self.self指向的也同樣是window磷箕。
Q:為什么使用self.self和global.global能判斷它就是window或者global呢?
保存會(huì)用到的變量方法
//保存原來的下劃線變量意味
var previousUnderscore = root._;
// 數(shù)組原型阵难、對(duì)象原型岳枷、Symbol原型(如果支持Symbol)
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
// 保存數(shù)組的push、slice呜叫、toString方法空繁,保存Object的hasOwnProperty方法
var push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// 在ES5中會(huì)使用到的方法:判斷是否為數(shù)組,返回鍵值組成的數(shù)組朱庆,創(chuàng)建一個(gè)對(duì)象
var nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeCreate = Object.create;
擴(kuò)展回憶:
使用Object.create(原型)盛泡,如果填入的參數(shù)為null,就可以創(chuàng)建一個(gè)沒有原型的對(duì)象娱颊。
處理參數(shù)和函數(shù)
var Ctor = function(){};
定義一個(gè)空殼函數(shù)Ctor來方便做原型的替代
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
定義我們自己的_
變量傲诵,是一個(gè)可以傳入一個(gè)obj的函數(shù)凯砍。如果這個(gè)obj是的實(shí)例,就返回這個(gè)obj掰吕。如果不是的話就new一個(gè)(obj)返回果覆,new操作會(huì)把新對(duì)象的原型指向_,this會(huì)指向新對(duì)象殖熟,并且會(huì)執(zhí)行this.wrapped = obj
局待。所以執(zhí)行這個(gè)函數(shù)的話,會(huì)得到一個(gè)類似于下圖的結(jié)構(gòu):
所以這個(gè)對(duì)象就成為了_的一個(gè)實(shí)例菱属。
下面是對(duì)于在node環(huán)境中的處理此處還沒找到實(shí)驗(yàn)環(huán)境钳榨,先跳過:
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';
處理參數(shù)
func:函數(shù)
context:上下文
argCount:參數(shù)個(gè)數(shù)
這里直接看其實(shí)也感覺很難理解薛耻,所以我會(huì)先在后面提取一個(gè)例子,結(jié)合文檔的用法來搞清楚這個(gè)函數(shù)到底的作用赏陵。
var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
//如果沒有傳入argCount饼齿,進(jìn)入3,否則對(duì)應(yīng)進(jìn)入不同的參數(shù)
switch (argCount == null ? 3 : argCount) {
// 返回一個(gè)直接傳入value的函數(shù)
case 1: return function(value) {
return func.call(context, value);
};
// 兩個(gè)參數(shù)的情況省略了蝙搔,因?yàn)槲覀儾粫?huì)用到
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
是什么意思呀缕溉?
關(guān)于void 0
這是在stackOverflow上找到的一個(gè)回答,void 0可以和undefined有同樣的作用吃型,但是它的字節(jié)要少一些所以用了它來減少尺寸哈哈哈证鸥。 這里使用了argCount == null,argCount如果沒有傳入實(shí)際上是undefined勤晚,但是undefined == null枉层,所以沒有傳入argCount會(huì)直接進(jìn)行3的分支。
一個(gè)內(nèi)置的函數(shù)來生成能調(diào)用多次調(diào)用回調(diào)函數(shù):
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);
};
處理剩余傳入的多個(gè)參數(shù)赐写,化為一個(gè)數(shù)組鸟蜡,類似于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);
};
};
創(chuàng)建一個(gè)對(duì)象
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;
};
返回對(duì)象的屬性值(淺返回)
var shallowProperty = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key];
};
};
按照path數(shù)組,返回一組屬性
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;
};
幫助集合來定義它是否為一個(gè)集合挺邀。應(yīng)該以數(shù)組/對(duì)象的方式來循環(huán)嗎揉忘?
//數(shù)組索引最大值
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
//獲取length值
var getLength = shallowProperty('length');
//是不是類數(shù)組?
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};