JS進(jìn)階——underscore源碼(1)

目錄

說(shuō)明

underscore是一個(gè)很有用的javaScript工具庫(kù)丁寄,對(duì)函數(shù)式編程提供很多方法涮帘,所以讀源碼很適合了解函數(shù)式編程顶瞳,筆者讀的是1.8.3版独令,代碼和注釋都為手寫摔桦,又不懂得也參考了網(wǎng)上資料社付,然后用了es6的let代替了var,等讀完會(huì)將封裝過(guò)的js文件放到github上:

_.VERSION = '1.8.3';

立即執(zhí)行函數(shù)

underscore的最外層是一個(gè)立即執(zhí)行函數(shù)酣溃,所有內(nèi)容都放在函數(shù)內(nèi)部瘦穆,利用的思想是閉包。
(function ( ) {...}) ()
形成一個(gè)獨(dú)立的作用域赊豌,這樣的好處是可以不污染全局扛或,也可以防止其他因素對(duì)內(nèi)部函數(shù)的影響

全局變量聲明

 let root = (
                (typeof self == 'object') &&
                // self表示window窗口自身,這是瀏覽器環(huán)境下的全局命名空間
                (self.self === self) &&
                // 如果存在self碘饼,判斷self是否是自身引用熙兔,即window這一對(duì)象
                (self)
                // 如果以上都滿足,說(shuō)明全局對(duì)象是window艾恼,并返回window作為root住涉,這里self即window
             ) ||
             (
                 (typeof global == 'object') &&
                 // global表示node環(huán)境下全局的命名空間
                 (global.global === global) &&
                 // 如果存在gloabl,判斷global是否是自身引用
                 (global)
                 // 如果以上都滿足钠绍,說(shuō)明全局對(duì)象是global舆声,并返回global作為root
            ) ||
            (this);
            // 如果以上兩者都不是,直接返回this,這里應(yīng)該處理既不是window這一瀏覽器環(huán)境,也不是global這一node環(huán)境的

該段代碼主要用于確認(rèn)環(huán)境的全局命名空間媳握,并賦值給變量root碱屁。
值得注意的是,網(wǎng)上很多源碼分析給出的是

var root = this;

但在1.8.3版本中查看了github上的源碼蛾找,發(fā)現(xiàn)它是最上面的封裝方式娩脾,這樣的好處有:

1、向前兼容嚴(yán)格模式打毛,在嚴(yán)格模式下直接使用this會(huì)得到undefined柿赊。這是因?yàn)閑cma262第5版中,為了防止人們誤將非new出來(lái)的對(duì)象的this指向全局命名空間幻枉,特地將之設(shè)置為undefined碰声。
2、用來(lái)支持WebWorker展辞,在WebWorker里可以使用self但不能使用window.

另一個(gè)值得注意的地方是:

(typeof self == 'object')

這里用的是==奥邮,而不是===。==和===的區(qū)別是一個(gè)不完全等罗珍,一個(gè)是全等洽腺,用==的好處是可以進(jìn)行轉(zhuǎn)義,因?yàn)槿置臻g不一定完全等價(jià)為對(duì)象覆旱,這跟瀏覽器的實(shí)現(xiàn)有關(guān)蘸朋。

避免命名沖突

let previousUnderscore = root._; // 將全局變量中的變量_賦值給變量previousUnderscore進(jìn)行緩存

previousUnderscore,從字面上理解就是“以前的 underscore”扣唱,最開始不明白這條語(yǔ)句的用意藕坯,但是從頭到尾只有一個(gè)地方用到了 previousUnderscore,即(1352行):

_.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };
// 使用noConflict方法返回自身

如果發(fā)生沖突噪沙,可以用例如

var underscore_cache = _.noConflict();

用來(lái)重新定義 underscore 命名.

_(初始化)

    let _ = function (obj) {
        if (obj instanceof _){
            return obj;
        }
        // 如果obj已經(jīng)是·_·函數(shù)的實(shí)例炼彪,則直接返回obj
        if (!(this instanceof _)) {
            return new _(obj);
        }
        // 如果不是`_`函數(shù)的實(shí)例
        // 則調(diào)用new運(yùn)算符,返回實(shí)例化對(duì)象
        this._wrapped = obj;
        // 將obj賦值給this._wrapped屬性
    };

其實(shí)核心函數(shù)——_其實(shí)是一個(gè)構(gòu)造函數(shù)正歼,支持無(wú)new調(diào)用的構(gòu)造函數(shù)辐马。當(dāng)使用函數(shù)式風(fēng)格的代碼時(shí)并不會(huì)有太大影響,但使用面向?qū)ο箫L(fēng)格的代碼時(shí)局义,這里省去了使用者調(diào)用new的麻煩喜爷。

node服務(wù)端中:

if (typeof exports != 'undefined' && !exports.nodeType) {
    if (typeof module != 'undefined' && !module.nodeType && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

這是 Node.js 中對(duì)通用模塊的封裝方法,通過(guò)對(duì)判斷 exports 是否存在來(lái)決定將局部變量 _ 賦值給exports萄唇,順便說(shuō)一下 AMD 規(guī)范檩帐、CMD規(guī)范和 UMD規(guī)范,Underscore.js 是支持 AMD 的另萤,在源碼尾部有定義湃密,這里簡(jiǎn)單敘述一下:

amd:AMDJS

define(['underscore'], function (_) {
    //todo
            });

cmd:Common Module Definition / draftCMD 模塊定義規(guī)范

var _ = require('underscore');
module.exports = _;

值得一提的是使用nodeType來(lái)確保exports和module并不是HTML的元素。

保存原型

let ArrayProto = Array.prototype,
    ObjProto = Object.prototype,
    SymbolProto = ((typeof(Symbol)) !== ("undefined")) ? (Symbol.prototype) : (null);
    // 緩存變量泛源,減少代碼量揍障,便于壓縮代碼
let push = ArrayProto.push,
    slice = ArrayProto.slice,
    // slice方法,可以從數(shù)組中返回選定的元素
    toString = ObjProto.toString,
    hasOwnProperty = ObjProto.hasOwnProperty;
    // 緩存變量俩由,減少代碼量,便于壓縮代碼
    // 同時(shí)可減少在原型鏈中的查找次數(shù)(提高代碼效率)
let nativeIsArray = Array.isArray,
    nativeKeys = Object.keys,
    // 返回一個(gè)給定對(duì)象的所有可枚舉屬性的字符串?dāng)?shù)組
    nativeCreate = Object.create;
// 可以調(diào)用create方法來(lái)創(chuàng)建對(duì)象癌蚁,對(duì)象的原型就是create方法的第一個(gè)參數(shù)
//ES5原生方法幻梯,如果瀏覽器支持,則underscore中會(huì)優(yōu)先使用

值得一提的是:
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
2009年的 ES5 規(guī)定了六種語(yǔ)言類型:Null Undefined Number Boolean String Object努释,詳見ES5/類型ES5/類型轉(zhuǎn)換與測(cè)試碘梢。新出臺(tái)的 ES6 則規(guī)定,包括六種原始類型:Null Undefined Number Boolean String 和 Symbol伐蒂,還有一種 Object煞躬,詳見JavaScript 數(shù)據(jù)類型和數(shù)據(jù)結(jié)構(gòu)。新增加的 Symbol 很早就已經(jīng)提出逸邦,其具體概念這里不再?gòu)?fù)述請(qǐng)移步參考 Symbol 恩沛,得益于 ES6 的漸漸普及,客戶端瀏覽器也有很多已經(jīng)支持 Symbol缕减,比如 Firefox v36+ 和 Chrome v38+ 等雷客,具體參考 ES6 支持情況,如果大家對(duì) ES6 想要深入了解可以看 ES6 In Depth 這篇文章和 ES6草案桥狡。

對(duì)象創(chuàng)建的特殊處理

let Ctor = function () {};  //用于代理轉(zhuǎn)換的空函數(shù)
let baseCraate = function (prototype) {
        if (!_.isObject(prototype)) {
            return {};
        }
        // 如果參數(shù)不是對(duì)象搅裙,直接返回空對(duì)象
        if (nativeCreate) {
            return nativeCreate(prototype);
        }
        //如果原生的對(duì)象創(chuàng)建可以使用,返回該方法根據(jù)原型創(chuàng)建的對(duì)象裹芝。
        //處理沒有原生對(duì)象創(chuàng)建的情況
        Ctor.prototype = prototype;
        //將空函數(shù)的原型指向要使用的原型
        let result = new Ctor();
        //創(chuàng)建一個(gè)實(shí)例
        Ctor.prototype = null;
        // 恢復(fù)Ctor的原型給下次使用
        return result;
        //返回該實(shí)例
    };

為了處理Object.create的跨瀏覽器的兼容性部逮,underscore進(jìn)行了特殊的處理。原型是無(wú)法直接實(shí)例化的嫂易,因此我們先創(chuàng)建一個(gè)空對(duì)象兄朋,然后將其原型指向這個(gè)我們想要實(shí)例化的原型,最后返回該對(duì)象其一個(gè)實(shí)例炬搭。

回調(diào)處理

在看這個(gè)之前蜈漓,先解釋一下void 0是什么含義? 引文來(lái)源
在 JavaScript 中宫盔,假設(shè)我們想判斷一個(gè)是否是 undefined融虽,那么我們通常會(huì)這樣寫:

if(a === undefined){}

但是,JavaScript 中的 undefined 并不可靠灼芭,我們?cè)囍鴮戇@樣一個(gè)函數(shù):

function test(a) {
  var undefined = 1;
  console.log(undefined); // => 1
  if(a===undefined) {
    // ...
  }
}

現(xiàn)在我們能夠明確的有额,標(biāo)識(shí)符 undefined 并不能真正反映 “未定義”,所以我們得通過(guò)其他手段獲得這一語(yǔ)義。幸好 JavaScript 還提供了 void 運(yùn)算符巍佑,該運(yùn)算符會(huì)對(duì)指定的表達(dá)式求值茴迁,并返回受信的 undefined:

void expression

最常見的用法是通過(guò)以下運(yùn)算來(lái)獲得 undefined,表達(dá)式為 0 時(shí)的運(yùn)算開銷最杏┧ァ:

void 0;
// or
void(0);

在 underscore 中堕义,所有需要獲得 undefined 地方,都通過(guò) void 0 進(jìn)行了替代脆栋。
當(dāng)然倦卖,曲線救國(guó)的方式不只一種,我們看到包裹 jquery 的立即執(zhí)行函數(shù):

(function(window,undefined) {
    // ...
})(window)

在這個(gè)函數(shù)中椿争,我們沒有向其傳遞第二參數(shù)(形參名叫 undefined)怕膛,那么第二個(gè)參數(shù)的值就會(huì)被傳遞上 “未定義”,因此秦踪,通過(guò)這種方式褐捻,在該函數(shù)的作用域中所有的 undefined 都為受信的 undefined。

    // underscore內(nèi)部方法
    // 根據(jù)this指向(context參數(shù))以及argCount參數(shù)二次操作返回一些回調(diào)椅邓、迭代方法
    let optimizeCb = function (func, context, argCount) {
        if (context === void 0) {
            return func;
        }
        // void 0返回undefined柠逞,即未傳入上下文信息時(shí)直接返回相應(yīng)的函數(shù)

        switch (argCount) {
            // 如果傳入了argCount,那么參數(shù)數(shù)量為argCount希坚,如果傳入等價(jià)為null边苹,則為3,包括未傳值得情況
            // 1個(gè)參數(shù)的時(shí)候裁僧,只需要傳遞當(dāng)前值
            case 1: return function (value) {
                return func.call(context, value);
            };
            // 并沒有2個(gè)參數(shù)的時(shí)候个束,因?yàn)槟壳安]有用到2個(gè)參數(shù)的時(shí)候
            // 3個(gè)參數(shù)的時(shí)候,分別是當(dāng)前值聊疲、當(dāng)前索引以及整個(gè)集合
            // _.each茬底、_.map
            case 3:return function (value, index, collection) {
                return func.call(context, value, index, collection)
            };
            // 4個(gè)參數(shù)的時(shí)候,分別是累計(jì)值获洲、當(dāng)前值阱表、當(dāng)前索引以及整個(gè)集合
            //_.reduce、_reduceRight
            case 4:return function (accumulator, value, index, collection) {
                return func.call(context, accumulator, value, index, collection);
            };
        }
        // 如果都不符合上述的任一條件贡珊,直接使用apply調(diào)用相關(guān)函數(shù)
        return function () {
            return func.apply(context, arguments)
        }

其實(shí)不用上面的switch-case語(yǔ)句
直接執(zhí)行下面的return語(yǔ)句最爬,一樣的效果
不這樣做的原因call比apply快很多
apply在運(yùn)行前要對(duì)作為參數(shù)的數(shù)組進(jìn)行一系列的檢驗(yàn)和深拷貝,.call則沒有這些步驟
https://segmentfault.com/q/1010000007894513
http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3
http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.4

接下來(lái)门岔,是針對(duì)集合迭代的回調(diào)處理

    var builtinIteratee;
    // 設(shè)置變量保存內(nèi)置迭代
    var cb = function(value, context, argCount) {
        if (_.iteratee !== builtinIteratee) {return _.iteratee(value, context);}
        // 如果用戶修改了迭代器爱致,則使用新的迭代器
        if (value == null) {return _.identity;}
        // 如果不傳value,表示返回等價(jià)的自身
        if (_.isFunction(value)) {return optimizeCb(value, context, argCount);}
        // 如果傳入函數(shù)寒随,返回該函數(shù)的回調(diào)
        if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
        // 如果傳入對(duì)象糠悯,尋找匹配的屬性值
        return _.property(value);
        // 如果都不是帮坚,返回相應(yīng)的屬性訪問器
    };
    // 默認(rèn)的迭代器,是以無(wú)窮argCount為參數(shù)調(diào)用cb函數(shù)互艾。用戶可以自行修改试和。
    _.iteratee = builtinIteratee = function(value, context) {
        return cb(value, context, Infinity);
    };

剩余的處理

// 屬性訪問器生成。
// 通過(guò)傳入鍵名纫普,返回可以訪問以傳入對(duì)象為參數(shù)阅悍,并可以獲取該對(duì)象相應(yīng)鍵值對(duì)的函數(shù)
    let shallowProperty = function(key) {
        return function(obj) {
            return obj == null ? void 0 : obj[key];
        };
    };

// 額外參數(shù)。
// 等價(jià)于ES6的rest參數(shù)昨稼。它將起始索引后的參數(shù)放入一個(gè)數(shù)組中溉箕。
       let restArgs = function(func, startIndex) {
        startIndex = startIndex == null ? func.length - 1 : +startIndex;
        // startIndex為null時(shí),為函數(shù)聲明時(shí)的參數(shù)數(shù)量減1悦昵,即除了第一個(gè)參數(shù)后的其他參數(shù),否則為傳入的startIndex
        // 返回一個(gè)支持rest參數(shù)的函數(shù)
        return function() {
            // 校正參數(shù), 以免出現(xiàn)負(fù)值情況
            let length = Math.max(arguments.length - startIndex, 0),
                // 剩余參數(shù)是一個(gè)length長(zhǎng)度的數(shù)組
                rest = Array(length),
                index = 0;
            // 假設(shè)參數(shù)從2個(gè)開始: func(a,b,*rest)
            // 調(diào)用: func(1,2,3,4,5); 實(shí)際的調(diào)用是:func.call(this, 1,2, [3,4,5]);
            for (; index < length; index++) {
                rest[index] = arguments[index + startIndex];
            }
            // 根據(jù)rest參數(shù)不同, 分情況調(diào)用函數(shù), 需要注意的是, rest參數(shù)總是最后一個(gè)參數(shù), 否則會(huì)有歧義
            switch (startIndex) {
                case 0: return func.call(this, rest);
                // 起始索引為0晌畅,表示全部參數(shù)作為一個(gè)數(shù)組調(diào)用
                case 1: return func.call(this, arguments[0], rest);
                // 表示將第一個(gè)參數(shù)以及后續(xù)參數(shù)的數(shù)組但指,作為兩個(gè)參數(shù)進(jìn)行調(diào)用
                case 2: return func.call(this, arguments[0], arguments[1], rest);
                // 將第一個(gè)參數(shù)、第二個(gè)參數(shù)以及后續(xù)參數(shù)的數(shù)組作為三個(gè)參數(shù)進(jìn)行調(diào)用
            }
            // 如果不是上面三種情況, 而是更通用的apply
            let args = Array(startIndex + 1);
            // 先拿到前面參數(shù)
            for (index = 0; index < startIndex; index++) {
                args[index] = arguments[index];
            }
            // 拼接上剩余參數(shù)
            args[startIndex] = rest;
            return func.apply(this, args);
        };
    };

// 處理類數(shù)組對(duì)象抗楔。
// 類數(shù)組對(duì)象的長(zhǎng)度應(yīng)當(dāng)是一個(gè)非負(fù)整數(shù)
    let MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
    // 表示正無(wú)窮+∞
    let getLength = shallowProperty('length');
    // 獲取length屬性訪問器
    let isArrayLike = function(collection) {
        let length = getLength(collection);
        return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
    };
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棋凳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子连躏,更是在濱河造成了極大的恐慌剩岳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件入热,死亡現(xiàn)場(chǎng)離奇詭異拍棕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)勺良,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門绰播,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人尚困,你說(shuō)我怎么就攤上這事蠢箩。” “怎么了事甜?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵谬泌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我逻谦,道長(zhǎng)掌实,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任跨跨,我火速辦了婚禮潮峦,結(jié)果婚禮上囱皿,老公的妹妹穿的比我還像新娘。我一直安慰自己忱嘹,他們只是感情好嘱腥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拘悦,像睡著了一般齿兔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上础米,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天分苇,我揣著相機(jī)與錄音,去河邊找鬼屁桑。 笑死医寿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蘑斧。 我是一名探鬼主播靖秩,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竖瘾!你這毒婦竟也來(lái)了沟突?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捕传,失蹤者是張志新(化名)和其女友劉穎惠拭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庸论,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡职辅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了聂示。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐农。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖催什,靈堂內(nèi)的尸體忽然破棺而出涵亏,到底是詐尸還是另有隱情,我是刑警寧澤蒲凶,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布气筋,位于F島的核電站,受9級(jí)特大地震影響旋圆,放射性物質(zhì)發(fā)生泄漏宠默。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一灵巧、第九天 我趴在偏房一處隱蔽的房頂上張望搀矫。 院中可真熱鬧抹沪,春花似錦、人聲如沸瓤球。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卦羡。三九已至噪馏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绿饵,已是汗流浹背欠肾。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拟赊,地道東北人刺桃。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吸祟,于是被迫代替她去往敵國(guó)和親虏肾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持欢搜,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠谴轮,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 3,981評(píng)論 12 65
  • @轉(zhuǎn)自GitHub 介紹js的基本數(shù)據(jù)類型炒瘟。Undefined、Null第步、Boolean疮装、Number、Strin...
    YT_Zou閱讀 1,158評(píng)論 0 0
  • 簡(jiǎn)單假期……沒有圖片粘都,沒有文字廓推,沒有發(fā)圈……簡(jiǎn)單假期,簡(jiǎn)單的不能再簡(jiǎn)單了……這一刻翩隧,最簡(jiǎn)單的愛......
    CindyRen麗群閱讀 162評(píng)論 0 0
  • Mysql-python不支持python3.5樊展,所以需要使用pymysql代替方法很簡(jiǎn)單,就是在int.py文件...
    Chester_01e2閱讀 1,136評(píng)論 0 0
  • 每個(gè)家長(zhǎng)都希望自己的孩子學(xué)會(huì)分享堆生。于是分享专缠,就變成了育兒當(dāng)中永恒不變的話題之一。 在萱萱會(huì)自己抓東西吃的時(shí)候淑仆,我就...
    萱萱_媽媽閱讀 161評(píng)論 0 0