講在前面
這次選的計(jì)劃是源碼閱讀計(jì)劃,因?yàn)橹盎〞r(shí)間x重新再過了高程和權(quán)威,覺得得看一些實(shí)在的東西來提高自己党觅。因此選擇了閱讀一些優(yōu)秀的庫和插件還有框架來打磨打磨。這次學(xué)習(xí)計(jì)劃肯定不能僅僅只閱讀代碼寫注釋那么簡(jiǎn)單佣蓉。我打算更加細(xì)致一點(diǎn)酸舍。
準(zhǔn)備工作
第一個(gè)是工具,對(duì)于工具很多人可能會(huì)直接操起手中的代碼編輯器直接來閱讀榨为,這的確是閱讀的手段之一惨好,但是我這里要安利一個(gè)工具lambda-viewgithub
第二個(gè)是中文文檔,當(dāng)然如果只有英文文檔也是可以的。
第三個(gè)是測(cè)試用例随闺,這個(gè)和第二個(gè)可能會(huì)有點(diǎn)重復(fù)日川,但是如果想看一些細(xì)節(jié)還是看測(cè)試比較方便。
第四個(gè)比較重要矩乐,當(dāng)然有一些框架或者庫可能沒有龄句,那就是完整的源碼解析,這是用來作為最后看完對(duì)照自己思路是否正確的一個(gè)好方法散罕。這個(gè)方法必須在你先看完所有源碼之后再來看分歇,不然不會(huì)有太大效果。
第五個(gè) 一個(gè)調(diào)試工具欧漱,可以是chrome,可以是codepen职抡,也可以是自己寫的前端調(diào)試工具。
Underscore源碼學(xué)習(xí)
第一份我先挑一個(gè)簡(jiǎn)單的來學(xué)習(xí)误甚。一個(gè)工具庫的寫法足以讓我們?nèi)腴T缚甩。
先來看下簡(jiǎn)介:
Underscore一個(gè)JavaScript實(shí)用庫谱净,提供了一整套函數(shù)式編程的實(shí)用功能,但是沒有擴(kuò)展任何JavaScript內(nèi)置對(duì)象
而且作為一個(gè)比較成熟的庫它其實(shí)已經(jīng)有很詳細(xì)的注釋了,可以點(diǎn)這里看對(duì)照
作為這個(gè)庫的新手我們先看下這個(gè)庫感興趣的部分擅威,我比較感興趣的是數(shù)組這部分,因?yàn)樗瓷先ケ容^實(shí)用壕探。
數(shù)組(Arrays)
- first
- initial
- last
- rest
- compact
- flatten
- without
- union
- intersection
- difference
- uniq
- zip
- unzip
- object
- indexOf
- lastIndexOf
- sortedIndex
- findIndex
- findLastIndex
- range
我們也不從什么大局觀先看,就從簡(jiǎn)單的這部分看一下郊丛,每個(gè)函數(shù)對(duì)應(yīng)的方法名都很清晰李请。
從first開始,先看官方文檔調(diào)用用例。
first_.first(array, [n]) Alias: head, take?
返回array(數(shù)組)的第一個(gè)元素宾袜。傳遞 n參數(shù)將返回?cái)?shù)組中從第一個(gè)元素開始的n個(gè)元素捻艳。
返回?cái)?shù)組中前 n 個(gè)元素
然后用sublime打開從github上下載下來的文件夾,查看test目錄里的arrays.js
然后看first的斷言
assert.strictEqual(_.first([1,2,3]),1,'can pull out the first element of an array');assert.strictEqual(_([1,2,3]).first(),1,'can perform OO-style "first()"');assert.deepEqual(_.first([1,2,3],0),[],'returns an empty array when n <= 0 (0 case)');assert.deepEqual(_.first([1,2,3],-1),[],'returns an empty array when n <= 0 (negative case)');assert.deepEqual(_.first([1,2,3],2),[1,2],'can fetch the first n elements');assert.deepEqual(_.first([1,2,3],5),[1,2,3],'returns the whole array if n > length');
這樣我們好像可以根據(jù)這個(gè)斷言寫一個(gè)猜測(cè)的代碼了
function_first(array,n){vartemp=[];for(vari=0;i<=n;i++){temp.push(array[i])}returntemp}
然后用chrome跑一下,發(fā)現(xiàn)
當(dāng)測(cè)試n>0的時(shí)候正常庆猫,n=0的時(shí)候發(fā)現(xiàn)邊界不對(duì)认轨。我們改改
function _first(array,n) {? var temp = [];? for(var i = 1 ; i <= n; i++){? ? temp.push(array[i-1])? }? return temp}
然后我們?cè)贉y(cè)試一下大于數(shù)組長(zhǎng)度的數(shù)值時(shí)會(huì)發(fā)現(xiàn):
[1, 2, 3, 4, undefined]
返回的數(shù)組里面有個(gè)undefined這說明我們寫的還是不夠嚴(yán)謹(jǐn),再改一下
function_first(array,n){vartemp=[];vart=Math.min(array.length,n)for(vari=1;i<=t;i++){temp.push(array[i-1])}returntemp}
這樣根據(jù)斷言來看我們寫的函數(shù)已經(jīng)符合了月培,現(xiàn)在我們?cè)賮砜纯丛创a是怎么寫的嘁字。
為了便于瀏覽我從lambda-view中截取源碼_.first = _.head = _.take = function (array, n, guard) {if (((array) == (null)) || ((array.length) < (1))) return void (0) ;if (((n) == (null)) || (guard)) return array[0] ;return _.initial (array, (array.length) - (n)) ;
是不是乍一看和想象的不一樣。我們可以看到最后實(shí)現(xiàn)功能的是_.initial
我們看下官方文檔對(duì)它的描述
initial_.initial(array, [n])
返回?cái)?shù)組中除了最后一個(gè)元素外的其他全部元素杉畜。 在arguments對(duì)象上特別有用纪蜒。傳遞 n參數(shù)將從結(jié)果中排除從最后一個(gè)開始的n個(gè)元素.排除數(shù)組后面的 n 個(gè)元素
然后我們看下_.initial的源碼
_.initial = function(array, n, guard) {? ? return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));? };
我們先要找一下slice的定義,在源碼最開頭搜索
var ArrayProto = Array.prototype, ObjProto = Object.prototype;? var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;? // Create quick reference variables for speed access to core prototypes.? var push = ArrayProto.push,? ? ? slice = ArrayProto.slice,? ? ? toString = ObjProto.toString,? ? ? hasOwnProperty = ObjProto.hasOwnProperty;
所以slice就是?Array.prototype.slice
然后我們跑一下改編后的代碼
var a =[1,2,3,4]var n = 5? _first = _head = _take = function(array, n, guard) {? ? if (array == null || array.length < 1) return void 0;? ? if (n == null || guard) return array[0];? ? return _initial(array, array.length - n);? };? _initial = function(array, n, guard) {? ? return Array.prototype.slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));? };_first(a,n)[1, 2, 3, 4]
先不提這個(gè)實(shí)現(xiàn)好像復(fù)雜化了一點(diǎn)東西此叠,但是我們還是看看這個(gè)實(shí)現(xiàn)纯续。一層一層來看
因?yàn)間uard我們是沒有傳東西進(jìn)去的,一開始我也不了解為啥有這個(gè)參數(shù)。
然后我們可以看到注釋里有一句The guard check allows it to work with _.map. 具體可以去看stackoverflow.com回復(fù)灭袁。這里先不細(xì)展開
Math.max(0, array.length - (n == null || guard ? 1 : n))
等價(jià)于
Math.max(0,array.length - n)
然后我們知道slice() 接受一個(gè)或兩個(gè)參數(shù)(返回項(xiàng)的起始位置和結(jié)束位置) 從當(dāng)前數(shù)組中按要求返回新數(shù)組猬错。
call() 接受兩個(gè)參數(shù),一個(gè)是在其中運(yùn)行函數(shù)的作用域(this),一個(gè)是參數(shù)列表
所以
Array.prototype.slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
我們就知道了它處理的方式和我們寫的是相反的.
這個(gè)雖然寫的比我們復(fù)雜一點(diǎn)茸歧,但是它還是很嚴(yán)謹(jǐn)?shù)木氤础1热缥覀儧]有考慮到判斷傳進(jìn)來的數(shù)組一開始就是null,不存在的情況软瞎。
接下來我們就不需要像之前那么繁瑣的看array相關(guān)后面的代碼了逢唤。因?yàn)楹芏喽际怯迷膕lice方法來處理的。
從頭看起
當(dāng)我們了解了一個(gè)內(nèi)部函數(shù)的大致寫法我們應(yīng)該學(xué)習(xí)一下整體然后再從看下來有個(gè)更清晰的認(rèn)知涤浇。
從這部開始我們需要一個(gè)已經(jīng)加載了underscore的靜態(tài)頁面鳖藕,這樣方便我們從chrome直接調(diào)用里面的參數(shù)。
事實(shí)上我在上課的時(shí)候用ipad瀏覽過兩遍整個(gè)源碼只锭,我覺得新人不應(yīng)該去抓那些比較復(fù)雜的函數(shù)的實(shí)現(xiàn)吊奢,而是先把整個(gè)架構(gòu)搞搞懂,然后很多大牛的文章其實(shí)是已經(jīng)跳過這部分了,因此我將比較擴(kuò)展的把頭給理清楚页滚。先簡(jiǎn)單化整個(gè)流程。
//用閉包保存整個(gè)庫(function() {? // 在1.8.2版本其實(shí)下面這句只有 var root = this;? // 也就是只是把 this 賦值給局部變量 root? //但是1.8.3更新了是為了適應(yīng)Node環(huán)境下引用铺呵,確認(rèn)環(huán)境的全局命名空間,瀏覽器下是window,服務(wù)器上是globa裹驰,用self代替這兩者? ? var root = typeof self == 'object' && self.self === self && self ||? ? ? ? ? ? typeof global == 'object' && global.global === global && global ||? ? ? ? ? ? this;// 原來全局環(huán)境中的變量 `_` 賦值給變量 previousUnderscore 進(jìn)行緩存,這里不用管它片挂,它是為了以后noConflict才用? var previousUnderscore = root._;//很自然的能看出這里把原生的Array和Object保存到變量幻林,一個(gè)是為了方便引用,一個(gè)是為了壓縮代碼音念。? var ArrayProto = Array.prototype, ObjProto = Object.prototype;? var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;// 把ES5原生的方法緩存一下沪饺,以后函數(shù)調(diào)用的時(shí)候先判斷是不是有原生的方法,有就調(diào)用闷愤,沒有就用自己實(shí)現(xiàn)的那套~? var push = ArrayProto.push,? ? ? slice = ArrayProto.slice,? ? ? toString = ObjProto.toString,? ? ? hasOwnProperty = ObjProto.hasOwnProperty;? var nativeIsArray = Array.isArray,? ? ? nativeKeys = Object.keys,? ? ? nativeCreate = Object.create;//用于baseCreate函數(shù)里面整葡,我也不是很清楚為什么這么提前聲明,用于代理原型交換的空函數(shù)? var Ctor = function(){};// 這個(gè)就是安全引用對(duì)象的方法讥脐,先判斷入對(duì)象是不是_的實(shí)例遭居,如果是就直接返回obj,不然用new來實(shí)例化再返回,最后要把對(duì)象賦值給wrapped旬渠,其他函數(shù)里有用到? var _ = function(obj) {? ? if (obj instanceof _) return obj;? ? if (!(this instanceof _)) return new _(obj);? ? this._wrapped = obj;? };? // 導(dǎo)出Underscore對(duì)象給node.js俱萍,如果在瀏覽器環(huán)境中,順便把`_`添加給全局對(duì)象root? if (typeof exports != 'undefined' && !exports.nodeType) {? ? if (typeof module != 'undefined' && !module.nodeType && module.exports) {? ? ? exports = module.exports = _;? ? }? ? exports._ = _;? } else {? ? root._ = _;? }//當(dāng)前版本號(hào)告丢,? _.VERSION = '1.8.3';// 這段代碼一開始我也看暈了枪蘑,但仔細(xì)一看就是判斷參數(shù)個(gè)數(shù)然后返回調(diào)用,返回一些回調(diào)岖免、迭代方法岳颇,這里我把代碼收縮在下文慢慢分析? var optimizeCb = function(func, context, argCount) {? };? var builtinIteratee;? // callback 的縮寫 回調(diào)生成方法 很多地方用到,可以說理解這個(gè)和上面的就可以寫一個(gè)簡(jiǎn)單的庫了? var cb = function(value, context, argCount) {? };// 對(duì)cb的封裝觅捆,默認(rèn)的迭代器赦役,我們可以看到它是傳遞了一個(gè)無窮的值作為argCount傳入cb 所以具體我們要看下面的cb的分析? _.iteratee = builtinIteratee = function(value, context) {? ? return cb(value, context, Infinity);? };//等價(jià)于ES6的rest參數(shù)。它將起始索引后的參數(shù)放入一個(gè)數(shù)組中栅炒。這里我找個(gè)栗子舉一下讓大家理解/**var f = function(a, b, ...theArgs) {? ? ...}f(1, 2, 3, 4, 5) // a=1, b=2, theArgs=[3, 4, 5]請(qǐng)注意f的第三個(gè)參數(shù)掂摔,在聲明時(shí)以'...'開頭。這樣在實(shí)際調(diào)用時(shí)赢赊,函數(shù)的前兩個(gè)參數(shù)分別映射成a乙漓、b,從第三個(gè)參數(shù)開始,這些參數(shù)按照順序映射成名為theArgs的數(shù)組释移。**///具體我們也單獨(dú)拉出來講? var restArgs = function(func, startIndex) { };//創(chuàng)建一個(gè)繼承其他函數(shù)的新對(duì)象,就是一個(gè)原型式繼承? var baseCreate = function(prototype) {? };? //獲取對(duì)象的屬性的鍵值? var shallowProperty = function(key) {? };? ? var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;? var getLength = shallowProperty('length');? ? //判斷是不是類數(shù)組叭披,所謂類數(shù)組就是即擁有 length 屬性并且 length 屬性值為 Number 類型的元素,數(shù)組玩讳,包括類似 {length: 10} 這樣的對(duì)象涩蜘,字符串嚼贡、函數(shù)? ? var isArrayLike = function(collection) {? ? var length = getLength(collection);? ? return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;? };? ? ....一堆函數(shù) // 兼容 AMD 規(guī)范/* 這么寫是因?yàn)閍md是這么調(diào)用的:define(['underscore'], function ( _) {function a(){}; // 私有方法,因?yàn)闆]有被返回(見下面)function b(){}; // 公共方法同诫,因?yàn)楸环祷亓薴unction c(){}; // 公共方法粤策,因?yàn)楸环祷亓? ? //? ? 暴露公共方法? ? return {? ? ? ? b: b,? ? ? ? c: c? ? }});*/if (typeof define == 'function' && define.amd) {? ? define('underscore', [], function() {? ? ? return _;? ? });? }}());
快速過了一遍整個(gè)underscore的結(jié)構(gòu),接下來我們就對(duì)之前縮放的內(nèi)容進(jìn)行更詳細(xì)的學(xué)習(xí).
optimizeCb:var optimizeCb = function(func, context, argCount) {? ? // 沒有上下文直接返回函數(shù)? ? if (context === void 0) return func;? ? ? ? // 對(duì)傳進(jìn)來的參數(shù)個(gè)數(shù)進(jìn)行判斷误窖,不同個(gè)數(shù)不同調(diào)用方式? ? // 接下來的switch其實(shí)只是一個(gè)傳遞參數(shù)規(guī)范例子0-0沒有什么軟用叮盘。自己寫的時(shí)候肯定會(huì)把這段去掉,因?yàn)橐膊挥绊懪场榱嗽谝阎獏?shù)數(shù)量的情況下讓 js 引擎做出優(yōu)化(避免使用 arguments)? ? switch (argCount == null ? 3 : argCount) {? ? ? 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);? ? };? };
再來看cb
cb: var cb = function(value, context, argCount) {? //如果用戶修改了迭代器柔吼,則使用新的迭代器 因?yàn)閎uiltinIteratee我們可以在下面看到是有定義的,只有當(dāng)默認(rèn)迭代器被修改了_.iteratee !== builtinIteratee才會(huì)返回true? ? if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);? ? /*? ? ? _.identity = function(value) {? ? ? ? ? ? return value;? ? ? ? ? };? ? /*? ? // 如果傳入的值是空丙唧,那么就表示返回等價(jià)的自身? ? if (value == null) return _.identity;? ? // 如果是函數(shù)愈魏,就返還該函數(shù)的調(diào)用? ? if (_.isFunction(value)) return optimizeCb(value, context, argCount);? ? // 如果是對(duì)象或者數(shù)組,尋找匹配的屬性值? ? if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);? ? //如果都不是艇棕,返相應(yīng)的屬性訪問器? ? return _.property(value);? };
這段代碼其它都挺好理解蝌戒,就是value相當(dāng)于傳進(jìn)來的一個(gè)附加條件,可能這個(gè)value名字起的太有迷惑性沼琉,一開始我以為就是個(gè)值或者對(duì)象北苟。但是看到后面以及看到其它函數(shù)調(diào)用它的方式就明白了:
_.filter = _.select = function(obj, predicate, context) {? ? var results = [];? ? predicate = cb(predicate, context);? ? ? ? _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
至于為什么有if (value == null) return _.identity;這句是因?yàn)橛蟹N情況下你可能不傳值進(jìn)來,比如
_.filter([1,2,3]) =>[1, 2, 3]
接下來是restArgs
restArgs:? ? // 傳入一個(gè)函數(shù)打瘪,一個(gè)開始位置標(biāo)志? var restArgs = function(func, startIndex) {? ? //這個(gè)函數(shù)可以把一個(gè)函數(shù)func的參數(shù)"改造"成Rest Parameters,如果不傳第二個(gè)參數(shù)startIndex友鼻,默認(rèn)用最后一個(gè)參數(shù)收集其余參數(shù)? /*? ? _.delay = restArgs(function(func, wait, args) {? ? return setTimeout(function() {? ? ? return func.apply(null, args);? ? }, wait);? });? ? 比如這個(gè)函數(shù)一開始就用restArgs處理,而且沒有傳startIndex闺骚,因此將會(huì)默認(rèn)用args來收集"剩余參數(shù)"? ? */? ? startIndex = startIndex == null ? func.length - 1 : +startIndex;? ? return function() {? ? ? var length = Math.max(arguments.length - startIndex, 0),? ? ? ? ? rest = Array(length),? ? ? ? ? index = 0;? ? ? //收集一個(gè)個(gè)剩余參數(shù)? ? ? for (; index < length; index++) {? ? ? ? rest[index] = arguments[index + startIndex];? ? ? }? ? ? //估摸著也是為了優(yōu)化先判斷幾個(gè)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;? ? ? // 一次性apply掉彩扔。? ? ? return func.apply(this, args);? ? };? };
最后我們看下baseCreate
baseCreate:? 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;? };Ctor我們之前提到了這個(gè)是空對(duì)象,用于代理原型交換的空函數(shù)僻爽〕娴铮可能這樣不是很眼熟,我扔一個(gè)例子原型式繼承function inheritObject(o){? function F(){}? F.prototype = o? return new F()}var book = {? name:"books",? allbooks:['css','html']}var a1 = inheritObject(book)a1.allbooks.push('js')var a2 = inheritObject(book)console.log(a2.allbooks) //["css", "html", "js"]這個(gè)估計(jì)就能看懂胸梆。啥也不用說了0-0敦捧,就一個(gè)正規(guī)的原型式繼承。
接下來我們可要進(jìn)入正文了碰镜,是的前面基本屬于鋪墊2333
我們學(xué)習(xí)一個(gè)庫的源碼會(huì)發(fā)現(xiàn)其中一些功能在自己日常中肯定是用不到的兢卵,自己造一個(gè)輪子又無從下手,那么基于一個(gè)輪子的改造我覺得至少我們都會(huì)绪颖。因此接下來是對(duì)整個(gè)庫的函數(shù)分析提取自己想要的秽荤,然后看看是不是能根據(jù)之前的學(xué)習(xí)搞出一個(gè)迷你版的underscore.當(dāng)然不能因?yàn)楸容^簡(jiǎn)單就復(fù)制粘貼。至少要手打一遍來加深理解。
而且敲的過程中我們可以嘗試去掉一些代碼看看能不能簡(jiǎn)化
首先我們?yōu)榱藴y(cè)試需要建立一個(gè)test.html 然后引入我們的tools.js文件窃款,然后我們先把整個(gè)架構(gòu)仿照Underscore搭起來课兄。
/** * Easy Tools Function * Learning Underscore * 2016-10-26 */(function() {? var root = typeof self =='object' && self.self === self && self || typeof global == 'object'? ? ? ? ? ? && global.global === global && global || this;? // 先緩存一下,萬一以后要用到呢? var preRoot = root._;? // 保存Native方法,為了方便引用和壓縮代碼雁乡,把ES5原生的方法緩存一下第喳,以后函數(shù)調(diào)用的時(shí)候先判斷是不是有原生的方法,有就調(diào)用踱稍,沒有就用自己實(shí)現(xiàn)的那套? var ArrayProto = Array.prototype, ObjProto = Object.prototype;? var push = ArrayProto.push,? ? ? slice = ArrayProto.slice,? ? ? toString = ObjProto.toString,? ? ? hasOwnProperty = ObjProto.hasOwnProperty;? var nativeIsArray = Array.isArray,? ? ? nativeKeys = Object.keys,? ? ? nativeCreate = Object.create;? /**? * Main Function? */? // 安全引用對(duì)象的方法,先判斷傳入對(duì)象是不是_的實(shí)例悠抹,如果是就直接返回obj珠月,不然用new來實(shí)例化再返回,最后要把對(duì)象賦值給wrapped,其他函數(shù)里有用到? var _ = function(obj) {? ? if (obj instanceof _) return obj;? ? if (!(this instanceof _)) return new _(obj);? ? this._wrapped = obj;? };? _.VERSION = '0.0.1';? // 導(dǎo)出Underscore對(duì)象給node.js楔敌,如果在瀏覽器環(huán)境中啤挎,順便把`_`添加給全局對(duì)象root? if (typeof exports != 'undefined' && !exports.nodeType) {? ? if (typeof module != 'undefined' && !module.nodeType && module.exports) {? ? ? exports = module.exports = _;? ? }? ? exports._ = _;? } else {? ? root._ = _;? }? /** 基礎(chǔ)判斷函數(shù)? */? // 處理一些瀏覽器下的Bug? var nodelist = root.document && root.document.childNodes;? if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {? ? _.isFunction = function(obj) {? ? ? return typeof obj == 'function' || false;? ? };? }? _.isObject = function(obj) {? ? var type = typeof obj;? ? return type === 'function' || type === 'object' && !!obj;? };? /** 庫處理函數(shù) */? // 判斷參數(shù)個(gè)數(shù)然后返回調(diào)用 刪去了原來那個(gè)優(yōu)化代碼,使其看起來更加簡(jiǎn)便? var optimizeCb = function(func, context, argCount) {? ? if (context === void 0) return func;? ? return function() {? ? ? return func.apply(context, arguments);? ? };? };? var originIteratee;? // callback 回調(diào)生成方法? var cb = function(value, context, argCount){? ? if (_.iteratee !== originIteratee) return _.iteratee(value, context);? ? if (value == null) return _.identity;? ? if (_.isFunction(value)) return optimizeCb(value, context, argCount);? ? if (_.isObejct(value) && !_.isArray(value)) return _.matcher(value);? ? return _.property(value);? };? // 對(duì)cb的封裝,默認(rèn)的迭代器卵凑,我們可以看到它是傳遞了一個(gè)無窮的值作為argCount傳入cb? _.iteratee = originIteratee = function(value, context) {? ? return cb(value, context, Infinity);? };? // 等價(jià)于ES6的rest參數(shù)庆聘。它將起始索引后的參數(shù)放入一個(gè)數(shù)組中? var restArgs = 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];? ? ? }? ? ? var args = Array(startIndex + 1);? ? ? for (index = 0; index < startIndex; index++) {? ? ? ? args[index] = arguments[index];? ? ? }? ? ? args[startIndex] = rest;? ? ? return func.apply(this, args);? ? };? };? // 原型式繼承? var Ctor = {};? 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];? ? };? };? var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;? var getLength = shallowProperty('length');? //判斷是不是類數(shù)組,所謂類數(shù)組就是即擁有 length 屬性并且 length 屬性值為 Number 類型的元素勺卢,數(shù)組伙判,包括類似 {length: 10} 這樣的對(duì)象,字符串黑忱、函數(shù)? var isArrayLike = function(collection) {? ? var length = getLength(collection);? ? return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;? };? // 數(shù)組處理函數(shù)? /*? * [返回?cái)?shù)組中除了最后一個(gè)元素外的其他全部元素]? * [傳遞 n參數(shù)將從結(jié)果中排除從最后一個(gè)開始的n個(gè)元素]? * _.initial([5, 4, 3, 2, 1]);? * =>[5, 4, 3, 2]? *? * _.initial([1, 2, 3, 4], 2)? * =>[1, 2]? */? _.initial = function(array, n, guard) {? ? return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));? };? /**? * [返回array(數(shù)組)的第n個(gè)元素]? *? _.first([1, 2, 3], 2)? *? =>[1, 2]? */? _.first = function(array, n, guard) {? ? if (array == null || array.length < 1) return void 0;? ? if (n == null || guard) return array[0];? ? return _.initial(array, array.length - n);? };}());
可以看到簡(jiǎn)單的整理了一下宴抚,然后就是一個(gè)個(gè)函數(shù)看過來,覺得有用的把它一個(gè)個(gè)添加進(jìn)來就可以了甫煞」角或許看到最后我們還可以把整個(gè)不需要的部分再刪去一部分。
這樣我們從數(shù)組這邊的函數(shù)一個(gè)個(gè)翻覺得自己能在開發(fā)中實(shí)用的函數(shù)會(huì)遇到一個(gè)問題抚吠,那就是我們之前沒有仔細(xì)看過集合那塊的函數(shù)常潮,而數(shù)組這邊有不少復(fù)雜的處理都會(huì)用到,因此我們?cè)诳创a的時(shí)候需要回翻哪個(gè)函數(shù)依賴了哪些集合的處理函數(shù)楷力。而且我們需要暫時(shí)姑且認(rèn)為這個(gè)庫的解決方式是比較最優(yōu)的解喊式,當(dāng)我們以后看別的庫發(fā)現(xiàn)有不同實(shí)現(xiàn)方式時(shí),我們需要回到這個(gè)庫再進(jìn)行對(duì)比弥雹。
首先我們來看一個(gè)典型例子:
_.uniq = function(array, isSorted, iteratee, context) {? ? // 判斷是不是已經(jīng)排序的垃帅,如果不是那么改變參數(shù)變成? ? // _.uniq(array, false, undefined, iteratee)? ? if (!_.isBoolean(isSorted)) {? ? ? context = iteratee;? ? ? iteratee = isSorted;? ? ? isSorted = false;? ? }? ? // 這里iteratee是一個(gè)自定義的迭代函數(shù),它可以指定你想要排重的那一項(xiàng)? ? // 在單元測(cè)試arrays文件里中有這么一條,我將它提出來? /* var list = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}, {name: 'Curly'}];? ? var expected = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}];? ? var iterator = function(stooge) { return stooge.name; };_.uniq(list, false, iterator)? ? 函數(shù)將對(duì)這個(gè)指定的對(duì)象里的name屬性進(jìn)行迭代排重*/? ? if (iteratee != null) iteratee = cb(iteratee, context);? ? // 保存最終結(jié)果? ? var result = [];? ? // 保存上一個(gè)元素? ? var seen = [];? ? for (var i = 0, length = getLength(array); i < length; i++) {? ? ? var value = array[i],? ? ? ? // 如果指定了迭代函數(shù)則對(duì)每一個(gè)數(shù)組里的值進(jìn)行迭代? ? ? ? ? computed = iteratee ? iteratee(value, i, array) : value;? ? ? // 如果已經(jīng)排序了? ? ? if (isSorted) {? ? ? ? // 將迭代后的值和保存的上一個(gè)變量進(jìn)行對(duì)比看是不是重復(fù)的? ? ? ? if (!i || seen !== computed) result.push(value);? ? ? ? seen = computed;? ? // 如果存在自定義的迭代? ? ? } else if (iteratee) {? ? ? ? // 不然直接在整個(gè)seen[]數(shù)組中找這個(gè)元素是不是存在,不存在就保存到結(jié)果里,這樣能保證每一個(gè)元素都是唯一的? ? ? ? if (!_.contains(seen, computed)) {? ? ? ? ? seen.push(computed);? ? ? ? ? result.push(value);? ? ? ? }? ? // 不存在自定義的迭代那么直接對(duì)整個(gè)傳入的數(shù)組的元素來判斷是否唯一? ? ? } else if (!_.contains(result, value)) {? ? ? ? result.push(value);? ? ? }? ? }? ? return result;? };
我們看到里面有一個(gè)_.contains剪勿,這個(gè)就是集合里的一個(gè)函數(shù)我們可以看到它是這么寫的
// 最終是通過indexOf來判斷這個(gè)元素是不是在數(shù)組或者對(duì)象里面贸诚。 // Underscore自己實(shí)現(xiàn)了一個(gè)createIndexFinder來創(chuàng)建indexOf和lastIndexOf,非常的長(zhǎng),這是為了針對(duì)不存在ES5原生函數(shù)IndexOf和lastIndexOf的處理方式酱固,但是我們基本就是依賴瀏覽器來開發(fā)的,而且我在node里也發(fā)現(xiàn)IndexOf是存在的械念,那么我們就不需要考慮用它的方案直接調(diào)用對(duì)象的indexOf即可。? _.contains? = function(obj, item, fromIndex, guard) {? ? // 如果是類數(shù)組就把整個(gè)類數(shù)組的值放進(jìn)一個(gè)數(shù)組里运悲,然后用數(shù)組的IndexOf方法來判斷是否包含? ? if (!isArrayLike(obj)) obj = _.values(obj);? ? if (typeof fromIndex != 'number' || guard) fromIndex = 0;? ? // 原生? ? // return _.indexOf(obj, item, fromIndex) >= 0;? ? // 改造后? ? return obj.indexOf(item, fromIndex) >= 0;? };? // 將一個(gè)對(duì)象的所有 values 值放入數(shù)組中? _.values = function(obj) {? ? var keys = _.keys(obj);? ? var length = keys.length;? ? var values = Array(length);? ? for (var i = 0; i < length; i++) {? ? ? values[i] = obj[keys[i]];? ? }? ? return values;? };
結(jié)尾
大概也是把自己想要的部分給過了一遍龄减,然而`Underscore`其實(shí)還有很多部分在本文并沒有提及,因此感興趣的可以自己按照我上面的方式去閱讀源碼班眯。當(dāng)然我在之后的日子里會(huì)慢慢將這個(gè)系列給填完希停。
下面是關(guān)于本文參考的資料
GitHub - hanzichi/underscore-analysis: underscore.js 源碼解讀 & 系列文章(未完待續(xù)..
推薦看源碼分析中的產(chǎn)物。
還有就是我一直以為知乎文章可以直接md格式粘貼過來就行署隘。宠能。。沒想到還要自己編輯磁餐。违崇。。
所以以后更新可能還是在博客那邊诊霹。