打造屬于自己的underscore系列 ( 一 )

underscore作為開發(fā)中比較常用的一個javascript工具庫,提供了一套豐富的函數(shù)式編程功能垮兑,該庫并沒有拓展原有的javascript原生對象,而是在自定義的_對象上漱挎,提供了100多個方法函數(shù)系枪。在這個系列中,將從uderscore源碼角度磕谅, 打造一個自己的underscore框架

一,框架設(shè)計(jì)

1.1 自執(zhí)行函數(shù)

現(xiàn)代js 庫的框架設(shè)計(jì)私爷,一般都是以自執(zhí)行函數(shù)的形式,自執(zhí)行函數(shù)一般有兩種形式

(function(){
    // 形式一
}())
(function(){
    // 形式二
})()

我們知道膊夹,函數(shù)聲明的形式會掛載到window對象作為方法存在衬浑,而函數(shù)表達(dá)式的形式則會掛載在window對象作為屬性存在,這都會造成變量污染放刨,而自執(zhí)行函數(shù)的好處在于可以防止變量的污染工秩,函數(shù)執(zhí)行完后便立刻銷毀掉。

1.2 使用風(fēng)格

underscore有兩種風(fēng)格形式可以使用,一種是面向?qū)ο箢愋屯刂睿硪环N是函數(shù)類型侵佃。

// 例子
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

因此,在定義underscore類的時候需要考慮對象和函數(shù)兩種場景奠支。當(dāng)以函數(shù)的形式調(diào)用時需要把 _ 當(dāng)作一個構(gòu)造函數(shù)并返回他的實(shí)例化馋辈。代碼如下

(function(root){
    var _ = function (obj) {
        if (!(this instanceof _)) {
          return new _(obj)
        }
    }
    root._ = _
}(this))

1.3 使用環(huán)境

現(xiàn)在前端開發(fā)重視模塊化,以node服務(wù)端而論倍谜, 我們有commonjs規(guī)范迈螟,以客戶端而論,我們有AMD 和 CMD規(guī)范尔崔,對應(yīng)的模塊加載器為 requirejs 和 seajs答毫。目前通行的javascript模塊規(guī)范主要集中在commonjs 和 AMD,因此為了讓定義的underscore庫能夠適用于各種規(guī)范季春。在框架的定義時需檢測使用環(huán)境并兼容各種規(guī)范洗搂。

  • 服務(wù)端:commonjs規(guī)范,檢測module.exports 是否存在载弄,滿足時通過 module.exports = {} 將 underscore暴露出來耘拇,不滿足則 通過window對象暴露出來。
  • 客戶端: AMD 規(guī)范宇攻, 檢測 define.amd 是否存在惫叛,滿足時通過 define('**', [], function(){ return '***' })暴露模塊
(function (root) {
  var _ = function (obj) {
    if (!(this instanceof _)) {
      return new _(obj)
    }
  }
  // commonjs 規(guī)范 檢測 module.exports 是否存在
  if ((typeof module !== 'undefined' && module.exports)) {
    module.exports = {
      _: _
    }
  } else {
    root._ = _;// window 對象暴露方法
  }
  // amd 規(guī)范,檢測 define.amd 是否存在
  if (typeof define == 'function' && define.amd) {
    define('underscore', [], function () {
      return _;
    });
  }

}(this))
1.3.1 服務(wù)端使用
// commonjs
const _ = require('./underscore.js')
console.log(_)
1.3.2 客戶端使用
// AMD
require(['underscore'], function (underscore) {
  console.log(underscore)
})

1.4 方法定義

underscore的調(diào)用逞刷,既可以通過_.unique(),也可以通過 _().unique()嘉涌,兩種方法效果相同卻需要在框架設(shè)計(jì)時定義兩套方法,一套是定義 _ 對象的靜態(tài)方法夸浅,另一套是擴(kuò)展 _對象原型鏈上的方法仑最。

_.uniqe = function() {}

_.prototype.unique = function() {}

為了避免冗余代碼,可以將定義好的靜態(tài)方法復(fù)制一份成為原型鏈上的方法

(function(root){
    ···
    _.mixins = function() {
        // 復(fù)制靜態(tài)方法到原型上
    }
    _.mixins() // 執(zhí)行方法
}(this))

mixins 方法的實(shí)現(xiàn)题篷,需要遍歷 underscore 對象上所有的靜態(tài)方法词身,因此需要先完成對 遍歷方法 _.each 的實(shí)現(xiàn)

1.41 _.each

_.each(list, iteratee, [context]) Alias: forEach
遍歷list中的所有元素,按順序用每個元素當(dāng)做參數(shù)調(diào)用 iteratee 函數(shù)番枚。如果傳遞了context參數(shù)法严,則把iteratee綁定到context對象上。每次調(diào)用iteratee都會傳遞三個參數(shù):(element, index, list)葫笼。如果list是個JavaScript對象深啤,iteratee的參數(shù)是 (value, key, list))。返回list以方便鏈?zhǔn)秸{(diào)用路星。

each 的第一個參數(shù)按照文檔可以支持 數(shù)組溯街,類數(shù)組诱桂,對象三種類型,數(shù)組類數(shù)組和對象在遍歷時的處理方式不同呈昔。前者回調(diào)函數(shù)處理的是 值和下標(biāo)挥等,后者處理的是 值和屬性。

// 判斷數(shù)組堤尾,類數(shù)組方法
(function(root) {
    ···
    _.each = function (list, callback, context) {
        // context 存在會改變callback 中this 的指向
        var i = 0;
        var key;
        if (isArrayLikeLike(list)) { //  數(shù)組肝劲,類數(shù)組
          for (var i = 0; i < list.length; i++) {
            context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)
          }
        } else { // 對象
          for (key in list) {
            context ? callback.call(context, list[key], key) : callback(list[key], key)
          }
        }
      }
    var isArrayLike = function (collection) {
        // 返回參數(shù) collection 的 length 屬性值
        var length = collection.length;
    
        // length是數(shù)值,非負(fù)郭宝,且小于等于MAX_ARRAY_INDEX
        // MAX_ARRAY_INDEX = Math.pow(2, 53) - 1
        return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
    }
}(this))


1.4.2 _.mixin

mixin方法的設(shè)計(jì)辞槐,目的是為了在underscore原型對象上擴(kuò)展更多的方法,它既可以用來擴(kuò)展用戶自定義的方法粘室,比如

_.mixin({
  capitalize: function(string) {
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  }
});
_("fabio").capitalize();
=> "Fabio"

當(dāng)然也可以用來內(nèi)部拷貝靜態(tài)方法到原型鏈的方法上榄檬。

(function(root){
    ···
    var push = Array.prototype.push
    var _ = function (obj) {
        if (!(this instanceof _)) {
          return new _(obj)
        }
        this.wrap = obj // 存儲實(shí)例對象傳過來的參數(shù)
      }
    _.mixins = function (obj) {
        _.each(obj, function (value, key) {
          _.prototype[key] = function () {
            var args = [this.wrap]
            push.apply(args, arguments)
            return value.apply(this, args)
          }
        })
      }
  _.mixins(_)
}(this))

其中關(guān)注點(diǎn)在arguments 的處理上,靜態(tài)方法需要傳遞目標(biāo)源作為方法的參數(shù) 即_.unique(目標(biāo)源, 回調(diào)函數(shù))衔统,而實(shí)例方法的目標(biāo)源存儲在構(gòu)造對象的屬性中 鹿榜,即_(目標(biāo)源).unique(回調(diào)函數(shù)),因此定義實(shí)例方法時需要合并屬性和回調(diào)函數(shù)。即Array.prorotype.push.apply([this.wrap], arguments),之后將他作為參數(shù)傳遞給靜態(tài)方法并返回處理結(jié)果缰冤。

將類數(shù)組轉(zhuǎn)成數(shù)組的方法

  • Array.prototype.slice.call(類數(shù)組)
  • var a = []; Array.prototype.push.apply(a, 類數(shù)組); console.log(a);
  • var a = []; Array.prototype.concat.apply(a, 類數(shù)組); console.log(a);
  • ES6方法 Array.from(類數(shù)組)
  • ES6擴(kuò)展運(yùn)算符 var args = [...類數(shù)組]

1.5 鏈?zhǔn)秸{(diào)用

1.5.1 _.chain()

返回一個封裝的對象. 在封裝的對象上調(diào)用方法會返回封裝的對象本身, 直道 value 方法調(diào)用為止犬缨。

underscore中方法的調(diào)用返回的是處理后的值,因此無法支持方法的鏈?zhǔn)秸{(diào)用棉浸。如果需要鏈?zhǔn)秸{(diào)用,需要使用chain()方法刺彩,chain的使用會使每次調(diào)用方法后返回underscore的實(shí)例對象迷郑,直到 調(diào)用value方法才停止返回。

(function(root){
    ···
    // chain方法會返回 _ 實(shí)例创倔,并且標(biāo)注該實(shí)例是否允許鏈?zhǔn)秸{(diào)用的
    _.chain = function(obj) {
        var instance = _(obj);
        instance.chain = true; 
        return instance
      }
    // 增加是否支持鏈?zhǔn)秸{(diào)用的判斷嗡害,如果支持,則返回該實(shí)例畦攘,不支持則直接返回結(jié)果霸妹,
    var chainResult = function (instance, obj) {
        return instance.chain ? _(obj).chain() : obj
      }
    _.mixins = function (obj) {
        _.each(obj, function (value, key) {
          _.prototype[key] = function () {
            var args = [this.wrap]
            push.apply(args, arguments)
            return chainResult(this, value.apply(this, args)) // 修改實(shí)例方法的返回值,返回值通過chainResult 包裝知押,根據(jù)chainResult的判斷結(jié)果改變返回值
          }
        })
      }
}(this))
1.5.2 value()

因?yàn)殒準(zhǔn)秸{(diào)用會使underscore的方法返回他的實(shí)例對象叹螟,所以當(dāng)需要結(jié)束這一調(diào)用行為時,需要使用value()台盯。 value()方法會返回調(diào)用的結(jié)果罢绽。

(function(root){
    ···
    _.value = function(instance) {
        return instance.wrap
    }
}(this))
未完待續(xù)。静盅。良价。

本文為博主原創(chuàng)文章,轉(zhuǎn)載請注明出處 https://www.cnblogs.com/kidflash/p/10077643.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市明垢,隨后出現(xiàn)的幾起案子蚣常,更是在濱河造成了極大的恐慌,老刑警劉巖痊银,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件史隆,死亡現(xiàn)場離奇詭異,居然都是意外死亡曼验,警方通過查閱死者的電腦和手機(jī)泌射,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鬓照,“玉大人熔酷,你說我怎么就攤上這事〔蝰桑” “怎么了拒秘?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長臭猜。 經(jīng)常有香客問我躺酒,道長,這世上最難降的妖魔是什么蔑歌? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任羹应,我火速辦了婚禮,結(jié)果婚禮上次屠,老公的妹妹穿的比我還像新娘园匹。我一直安慰自己,他們只是感情好劫灶,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布裸违。 她就那樣靜靜地躺著,像睡著了一般本昏。 火紅的嫁衣襯著肌膚如雪供汛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天涌穆,我揣著相機(jī)與錄音怔昨,去河邊找鬼。 笑死蒲犬,一個胖子當(dāng)著我的面吹牛朱监,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播原叮,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼赫编,長吁一口氣:“原來是場噩夢啊……” “哼巡蘸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起擂送,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤悦荒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嘹吨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搬味,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年蟀拷,在試婚紗的時候發(fā)現(xiàn)自己被綠了碰纬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡问芬,死狀恐怖悦析,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情此衅,我是刑警寧澤强戴,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站挡鞍,受9級特大地震影響骑歹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墨微,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一道媚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欢嘿,春花似錦衰琐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狸剃。三九已至掐隐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钞馁,已是汗流浹背虑省。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留僧凰,地道東北人探颈。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像训措,于是被迫代替她去往敵國和親伪节。 傳聞我的和親對象是個殘疾皇子光羞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359