源碼閱讀計劃之underscore數組篇

講在前面

這次選的計劃是源碼閱讀計劃,因為之前花時間x重新再過了高程和權威,覺得得看一些實在的東西來提高自己。因此選擇了閱讀一些優秀的庫和插件還有框架來打磨打磨。這次學習計劃肯定不能僅僅只閱讀代碼寫注釋那么簡單。我打算更加細致一點。

準備工作

第一個是工具,對于工具很多人可能會直接操起手中的代碼編輯器直接來閱讀,這的確是閱讀的手段之一,但是我這里要安利一個工具lambda-viewgithub

第二個是中文文檔,當然如果只有英文文檔也是可以的。

第三個是測試用例,這個和第二個可能會有點重復,但是如果想看一些細節還是看測試比較方便。

第四個比較重要,當然有一些框架或者庫可能沒有,那就是完整的源碼解析,這是用來作為最后看完對照自己思路是否正確的一個好方法。這個方法必須在你先看完所有源碼之后再來看,不然不會有太大效果。

第五個 一個調試工具,可以是chrome,可以是codepen,也可以是自己寫的前端調試工具。

Underscore源碼學習

第一份我先挑一個簡單的來學習。一個工具庫的寫法足以讓我們入門。

先來看下簡介:

Underscore一個JavaScript實用庫,提供了一整套函數式編程的實用功能,但是沒有擴展任何JavaScript內置對象

而且作為一個比較成熟的庫它其實已經有很詳細的注釋了,可以點這里看對照

作為這個庫的新手我們先看下這個庫感興趣的部分,我比較感興趣的是數組這部分,因為它看上去比較實用。

數組(Arrays)

- first

- initial

- last

- rest

- compact

- flatten

- without

- union

- intersection

- difference

- uniq

- zip

- unzip

- object

- indexOf

- lastIndexOf

- sortedIndex

- findIndex

- findLastIndex

- range

我們也不從什么大局觀先看,就從簡單的這部分看一下,每個函數對應的方法名都很清晰。

first開始,先看官方文檔調用用例。

first_.first(array, [n]) Alias: head, take?

返回array(數組)的第一個元素。傳遞 n參數將返回數組中從第一個元素開始的n個元素。

返回數組中前 n 個元素

然后用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');

這樣我們好像可以根據這個斷言寫一個猜測的代碼了

function_first(array,n){vartemp=[];for(vari=0;i<=n;i++){temp.push(array[i])}returntemp}

然后用chrome跑一下,發現

當測試n>0的時候正常,n=0的時候發現邊界不對。我們改改

function _first(array,n) {? var temp = [];? for(var i = 1 ; i <= n; i++){? ? temp.push(array[i-1])? }? return temp}

然后我們再測試一下大于數組長度的數值時會發現:

[1, 2, 3, 4, undefined]

返回的數組里面有個undefined這說明我們寫的還是不夠嚴謹,再改一下

function_first(array,n){vartemp=[];vart=Math.min(array.length,n)for(vari=1;i<=t;i++){temp.push(array[i-1])}returntemp}

這樣根據斷言來看我們寫的函數已經符合了,現在我們再來看看源碼是怎么寫的。

為了便于瀏覽我從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)) ;

是不是乍一看和想象的不一樣。我們可以看到最后實現功能的是_.initial

我們看下官方文檔對它的描述

initial_.initial(array, [n])

返回數組中除了最后一個元素外的其他全部元素。 在arguments對象上特別有用。傳遞 n參數將從結果中排除從最后一個開始的n個元素.排除數組后面的 n 個元素

然后我們看下_.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]

先不提這個實現好像復雜化了一點東西,但是我們還是看看這個實現。一層一層來看

因為guard我們是沒有傳東西進去的,一開始我也不了解為啥有這個參數。

然后我們可以看到注釋里有一句The guard check allows it to work with _.map. 具體可以去看stackoverflow.com回復。這里先不細展開

Math.max(0, array.length - (n == null || guard ? 1 : n))

等價于

Math.max(0,array.length - n)

然后我們知道slice() 接受一個或兩個參數(返回項的起始位置和結束位置) 從當前數組中按要求返回新數組。

call() 接受兩個參數,一個是在其中運行函數的作用域(this),一個是參數列表

所以

Array.prototype.slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));

我們就知道了它處理的方式和我們寫的是相反的.

這個雖然寫的比我們復雜一點,但是它還是很嚴謹的。比如我們沒有考慮到判斷傳進來的數組一開始就是null,不存在的情況。

接下來我們就不需要像之前那么繁瑣的看array相關后面的代碼了。因為很多都是用原生的slice方法來處理的。

從頭看起

當我們了解了一個內部函數的大致寫法我們應該學習一下整體然后再從看下來有個更清晰的認知。

從這部開始我們需要一個已經加載了underscore的靜態頁面,這樣方便我們從chrome直接調用里面的參數。

事實上我在上課的時候用ipad瀏覽過兩遍整個源碼,我覺得新人不應該去抓那些比較復雜的函數的實現,而是先把整個架構搞搞懂,然后很多大牛的文章其實是已經跳過這部分了,因此我將比較擴展的把頭給理清楚。先簡單化整個流程。

//用閉包保存整個庫(function() {? // 在1.8.2版本其實下面這句只有 var root = this;? // 也就是只是把 this 賦值給局部變量 root? //但是1.8.3更新了是為了適應Node環境下引用,確認環境的全局命名空間,瀏覽器下是window,服務器上是globa,用self代替這兩者? ? var root = typeof self == 'object' && self.self === self && self ||? ? ? ? ? ? typeof global == 'object' && global.global === global && global ||? ? ? ? ? ? this;// 原來全局環境中的變量 `_` 賦值給變量 previousUnderscore 進行緩存,這里不用管它,它是為了以后noConflict才用? var previousUnderscore = root._;//很自然的能看出這里把原生的Array和Object保存到變量,一個是為了方便引用,一個是為了壓縮代碼。? var ArrayProto = Array.prototype, ObjProto = Object.prototype;? var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;// 把ES5原生的方法緩存一下,以后函數調用的時候先判斷是不是有原生的方法,有就調用,沒有就用自己實現的那套~? var push = ArrayProto.push,? ? ? slice = ArrayProto.slice,? ? ? toString = ObjProto.toString,? ? ? hasOwnProperty = ObjProto.hasOwnProperty;? var nativeIsArray = Array.isArray,? ? ? nativeKeys = Object.keys,? ? ? nativeCreate = Object.create;//用于baseCreate函數里面,我也不是很清楚為什么這么提前聲明,用于代理原型交換的空函數? var Ctor = function(){};// 這個就是安全引用對象的方法,先判斷入對象是不是_的實例,如果是就直接返回obj,不然用new來實例化再返回,最后要把對象賦值給wrapped,其他函數里有用到? var _ = function(obj) {? ? if (obj instanceof _) return obj;? ? if (!(this instanceof _)) return new _(obj);? ? this._wrapped = obj;? };? // 導出Underscore對象給node.js,如果在瀏覽器環境中,順便把`_`添加給全局對象root? if (typeof exports != 'undefined' && !exports.nodeType) {? ? if (typeof module != 'undefined' && !module.nodeType && module.exports) {? ? ? exports = module.exports = _;? ? }? ? exports._ = _;? } else {? ? root._ = _;? }//當前版本號,? _.VERSION = '1.8.3';// 這段代碼一開始我也看暈了,但仔細一看就是判斷參數個數然后返回調用,返回一些回調、迭代方法,這里我把代碼收縮在下文慢慢分析? var optimizeCb = function(func, context, argCount) {? };? var builtinIteratee;? // callback 的縮寫 回調生成方法 很多地方用到,可以說理解這個和上面的就可以寫一個簡單的庫了? var cb = function(value, context, argCount) {? };// 對cb的封裝,默認的迭代器,我們可以看到它是傳遞了一個無窮的值作為argCount傳入cb 所以具體我們要看下面的cb的分析? _.iteratee = builtinIteratee = function(value, context) {? ? return cb(value, context, Infinity);? };//等價于ES6的rest參數。它將起始索引后的參數放入一個數組中。這里我找個栗子舉一下讓大家理解/**var f = function(a, b, ...theArgs) {? ? ...}f(1, 2, 3, 4, 5) // a=1, b=2, theArgs=[3, 4, 5]請注意f的第三個參數,在聲明時以'...'開頭。這樣在實際調用時,函數的前兩個參數分別映射成a、b,從第三個參數開始,這些參數按照順序映射成名為theArgs的數組。**///具體我們也單獨拉出來講? var restArgs = function(func, startIndex) { };//創建一個繼承其他函數的新對象,就是一個原型式繼承? var baseCreate = function(prototype) {? };? //獲取對象的屬性的鍵值? var shallowProperty = function(key) {? };? ? var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;? var getLength = shallowProperty('length');? ? //判斷是不是類數組,所謂類數組就是即擁有 length 屬性并且 length 屬性值為 Number 類型的元素,數組,包括類似 {length: 10} 這樣的對象,字符串、函數? ? var isArrayLike = function(collection) {? ? var length = getLength(collection);? ? return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;? };? ? ....一堆函數 // 兼容 AMD 規范/* 這么寫是因為amd是這么調用的:define(['underscore'], function ( _) {function a(){}; // 私有方法,因為沒有被返回(見下面)function b(){}; // 公共方法,因為被返回了function c(){}; // 公共方法,因為被返回了? ? //? ? 暴露公共方法? ? return {? ? ? ? b: b,? ? ? ? c: c? ? }});*/if (typeof define == 'function' && define.amd) {? ? define('underscore', [], function() {? ? ? return _;? ? });? }}());

快速過了一遍整個underscore的結構,接下來我們就對之前縮放的內容進行更詳細的學習.

optimizeCb:var optimizeCb = function(func, context, argCount) {? ? // 沒有上下文直接返回函數? ? if (context === void 0) return func;? ? ? ? // 對傳進來的參數個數進行判斷,不同個數不同調用方式? ? // 接下來的switch其實只是一個傳遞參數規范例子0-0沒有什么軟用。自己寫的時候肯定會把這段去掉,因為也不影響。為了在已知參數數量的情況下讓 js 引擎做出優化(避免使用 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) {? //如果用戶修改了迭代器,則使用新的迭代器 因為builtinIteratee我們可以在下面看到是有定義的,只有當默認迭代器被修改了_.iteratee !== builtinIteratee才會返回true? ? if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);? ? /*? ? ? _.identity = function(value) {? ? ? ? ? ? return value;? ? ? ? ? };? ? /*? ? // 如果傳入的值是空,那么就表示返回等價的自身? ? if (value == null) return _.identity;? ? // 如果是函數,就返還該函數的調用? ? if (_.isFunction(value)) return optimizeCb(value, context, argCount);? ? // 如果是對象或者數組,尋找匹配的屬性值? ? if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);? ? //如果都不是,返相應的屬性訪問器? ? return _.property(value);? };

這段代碼其它都挺好理解,就是value相當于傳進來的一個附加條件,可能這個value名字起的太有迷惑性,一開始我以為就是個值或者對象。但是看到后面以及看到其它函數調用它的方式就明白了:

_.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;這句是因為有種情況下你可能不傳值進來,比如

_.filter([1,2,3]) =>[1, 2, 3]

接下來是restArgs

restArgs:? ? // 傳入一個函數,一個開始位置標志? var restArgs = function(func, startIndex) {? ? //這個函數可以把一個函數func的參數"改造"成Rest Parameters,如果不傳第二個參數startIndex,默認用最后一個參數收集其余參數? /*? ? _.delay = restArgs(function(func, wait, args) {? ? return setTimeout(function() {? ? ? return func.apply(null, args);? ? }, wait);? });? ? 比如這個函數一開始就用restArgs處理,而且沒有傳startIndex,因此將會默認用args來收集"剩余參數"? ? */? ? 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];? ? ? }? ? ? //估摸著也是為了優化先判斷幾個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我們之前提到了這個是空對象,用于代理原型交換的空函數。可能這樣不是很眼熟,我扔一個例子原型式繼承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"]這個估計就能看懂。啥也不用說了0-0,就一個正規的原型式繼承。

接下來我們可要進入正文了,是的前面基本屬于鋪墊2333

我們學習一個庫的源碼會發現其中一些功能在自己日常中肯定是用不到的,自己造一個輪子又無從下手,那么基于一個輪子的改造我覺得至少我們都會。因此接下來是對整個庫的函數分析提取自己想要的,然后看看是不是能根據之前的學習搞出一個迷你版的underscore.當然不能因為比較簡單就復制粘貼。至少要手打一遍來加深理解。

而且敲的過程中我們可以嘗試去掉一些代碼看看能不能簡化

首先我們為了測試需要建立一個test.html 然后引入我們的tools.js文件,然后我們先把整個架構仿照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原生的方法緩存一下,以后函數調用的時候先判斷是不是有原生的方法,有就調用,沒有就用自己實現的那套? 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? */? // 安全引用對象的方法,先判斷傳入對象是不是_的實例,如果是就直接返回obj,不然用new來實例化再返回,最后要把對象賦值給wrapped,其他函數里有用到? var _ = function(obj) {? ? if (obj instanceof _) return obj;? ? if (!(this instanceof _)) return new _(obj);? ? this._wrapped = obj;? };? _.VERSION = '0.0.1';? // 導出Underscore對象給node.js,如果在瀏覽器環境中,順便把`_`添加給全局對象root? if (typeof exports != 'undefined' && !exports.nodeType) {? ? if (typeof module != 'undefined' && !module.nodeType && module.exports) {? ? ? exports = module.exports = _;? ? }? ? exports._ = _;? } else {? ? root._ = _;? }? /** 基礎判斷函數? */? // 處理一些瀏覽器下的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;? };? /** 庫處理函數 */? // 判斷參數個數然后返回調用 刪去了原來那個優化代碼,使其看起來更加簡便? var optimizeCb = function(func, context, argCount) {? ? if (context === void 0) return func;? ? return function() {? ? ? return func.apply(context, arguments);? ? };? };? var originIteratee;? // callback 回調生成方法? 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);? };? // 對cb的封裝,默認的迭代器,我們可以看到它是傳遞了一個無窮的值作為argCount傳入cb? _.iteratee = originIteratee = function(value, context) {? ? return cb(value, context, Infinity);? };? // 等價于ES6的rest參數。它將起始索引后的參數放入一個數組中? 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;? };? // 獲取對象的屬性的鍵值? 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');? //判斷是不是類數組,所謂類數組就是即擁有 length 屬性并且 length 屬性值為 Number 類型的元素,數組,包括類似 {length: 10} 這樣的對象,字符串、函數? var isArrayLike = function(collection) {? ? var length = getLength(collection);? ? return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;? };? // 數組處理函數? /*? * [返回數組中除了最后一個元素外的其他全部元素]? * [傳遞 n參數將從結果中排除從最后一個開始的n個元素]? * _.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(數組)的第n個元素]? *? _.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);? };}());

可以看到簡單的整理了一下,然后就是一個個函數看過來,覺得有用的把它一個個添加進來就可以了。或許看到最后我們還可以把整個不需要的部分再刪去一部分。

這樣我們從數組這邊的函數一個個翻覺得自己能在開發中實用的函數會遇到一個問題,那就是我們之前沒有仔細看過集合那塊的函數,而數組這邊有不少復雜的處理都會用到,因此我們在看代碼的時候需要回翻哪個函數依賴了哪些集合的處理函數。而且我們需要暫時姑且認為這個庫的解決方式是比較最優的解,當我們以后看別的庫發現有不同實現方式時,我們需要回到這個庫再進行對比。

首先我們來看一個典型例子:

_.uniq = function(array, isSorted, iteratee, context) {? ? // 判斷是不是已經排序的,如果不是那么改變參數變成? ? // _.uniq(array, false, undefined, iteratee)? ? if (!_.isBoolean(isSorted)) {? ? ? context = iteratee;? ? ? iteratee = isSorted;? ? ? isSorted = false;? ? }? ? // 這里iteratee是一個自定義的迭代函數,它可以指定你想要排重的那一項? ? // 在單元測試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)? ? 函數將對這個指定的對象里的name屬性進行迭代排重*/? ? if (iteratee != null) iteratee = cb(iteratee, context);? ? // 保存最終結果? ? var result = [];? ? // 保存上一個元素? ? var seen = [];? ? for (var i = 0, length = getLength(array); i < length; i++) {? ? ? var value = array[i],? ? ? ? // 如果指定了迭代函數則對每一個數組里的值進行迭代? ? ? ? ? computed = iteratee ? iteratee(value, i, array) : value;? ? ? // 如果已經排序了? ? ? if (isSorted) {? ? ? ? // 將迭代后的值和保存的上一個變量進行對比看是不是重復的? ? ? ? if (!i || seen !== computed) result.push(value);? ? ? ? seen = computed;? ? // 如果存在自定義的迭代? ? ? } else if (iteratee) {? ? ? ? // 不然直接在整個seen[]數組中找這個元素是不是存在,不存在就保存到結果里,這樣能保證每一個元素都是唯一的? ? ? ? if (!_.contains(seen, computed)) {? ? ? ? ? seen.push(computed);? ? ? ? ? result.push(value);? ? ? ? }? ? // 不存在自定義的迭代那么直接對整個傳入的數組的元素來判斷是否唯一? ? ? } else if (!_.contains(result, value)) {? ? ? ? result.push(value);? ? ? }? ? }? ? return result;? };

我們看到里面有一個_.contains,這個就是集合里的一個函數我們可以看到它是這么寫的

// 最終是通過indexOf來判斷這個元素是不是在數組或者對象里面。 // Underscore自己實現了一個createIndexFinder來創建indexOf和lastIndexOf,非常的長,這是為了針對不存在ES5原生函數IndexOf和lastIndexOf的處理方式,但是我們基本就是依賴瀏覽器來開發的,而且我在node里也發現IndexOf是存在的,那么我們就不需要考慮用它的方案直接調用對象的indexOf即可。? _.contains? = function(obj, item, fromIndex, guard) {? ? // 如果是類數組就把整個類數組的值放進一個數組里,然后用數組的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;? };? // 將一個對象的所有 values 值放入數組中? _.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;? };

結尾

大概也是把自己想要的部分給過了一遍,然而`Underscore`其實還有很多部分在本文并沒有提及,因此感興趣的可以自己按照我上面的方式去閱讀源碼。當然我在之后的日子里會慢慢將這個系列給填完。

下面是關于本文參考的資料

Underscore.js(1.8.3) 中文文檔

GitHub - hanzichi/underscore-analysis: underscore.js 源碼解讀 & 系列文章(未完待續..

推薦看源碼分析中的產物。

還有就是我一直以為知乎文章可以直接md格式粘貼過來就行。。。沒想到還要自己編輯。。。

所以以后更新可能還是在博客那邊。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容

  • 工廠模式類似于現實生活中的工廠可以產生大量相似的商品,去做同樣的事情,實現同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 7,796評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實例,加載時并不主動創建,需要時才創建 最常見的單例模式,...
    Obeing閱讀 2,085評論 1 10
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,863評論 0 38
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執行單位為行(line),也就是一...
    悟名先生閱讀 4,184評論 0 13
  • 1.pop出棧,push壓棧, push能跳轉到任意制定的頁面控制器,pop是返回到任意制定的控制器,但是其前提是...
    i得深刻方得S閱讀 845評論 0 1