先過濾掉underscore內部各個工具函數的具體邏輯,只看源碼庫自己有什麼內容。
underscore有兩種調用方式:javascript
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
_
是一個函數對象,api中的函數全都掛載到_
上,實現_.func
java
// 使用當即執行函數 (function () { // 定義全局對象 var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {}; // 省略... // 建立_對象 var _ = function(obj) { // 若是參數是underscore的一個實例,就直接返回該參數 if (obj instanceof _) return obj; // 對應_(xxx)調用,若是new實例不是_的實例,就返回實例化_對象 if (!(this instanceof _)) return new _(obj); // 並將數據對象包裝在實例對象上 this._wrapped = obj; }; // 省略... //註冊在全局對象 root._=_; })();
上一步中,咱們建立了underscore實例,只能支持_.func
調用,若是要支持_(obj).func
,同時還要將func
註冊在實例的prototype
上。試想下,若是每聲明一個函數,就要綁定一次,那得用多…編程
在underscore中,使用mixin
將自定義函數添加到Underscore對象。api
// 用於返回排序後的數組,包含全部的obj中的函數名。 _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; _.mixin = function(obj) { // 遍歷obj的函數,綁定在原型上 _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { // this._wrapped做爲第一個參數傳遞,其餘用戶傳遞的參數放在後面。 var args = [this._wrapped]; push.apply(args, arguments); return chainResult(this, func.apply(_, args)); }; }); return _; }; // 執行混入 _.mixin(_);
大體流程是:數組
_(args).func(argument)
參數進行合併_
自身定義一系列函數,經過_.mixin()
綁定在了_.prototype
上,提升了代碼的複用度。app
相似於 Java Stream 流式編程函數
在Javascript中,數據能夠像是在管道中流通,咱們稱之爲,聲明式編程/連接式調用。工具
data.filter(...).unique(...).map(...)
既然要知足連接式調用(Chaining)語法,須要知足兩個條件學習
可能會想到,在每一個函數結尾return this;
,對,能用可是不必,除了要手動添加外,咱們也沒法關閉鏈式。this
在underscore中,使用的是可選式實現連接編程,使用.chain()
來開啓鏈式調用,而後使用.value()
獲取函數的最終值。
關鍵函數:
// 開啓鏈式調用 _.chain = function (obj) { //返回一個封裝的對象. 在封裝的對象上調用方法會返回封裝的對象自己 var instance = _(obj) instance._chain = true //檢測對象是否支持鏈式調用 return instance } // 輔助函數:鏈式中轉 // 鏈式調用 將數據對象封裝在underscore實例中 // 非鏈式調用 返回數據對象 var chainResult = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _.prototype.value = function() { return this._wrapped; };
這次學習目標不是爲了學習api,而是經過將其設計思想轉換爲本身的。經過以上幾點,咱們能夠大概實現一個簡化版的underscore,
簡化版:
(function (root) { var _ = function (obj) { if (!(this instanceof _)) { return new _(obj) } this.warp = obj } _.unique = function (arr, callback) { var result = [] var item for (var i = 0; i < arr.length; i++) { item = callback ? callback(arr[i]) : arr[i] if (result.indexOf(item) === -1) { result.push(item) } } return result } // 獲取對象上的函數 _.functions = function (obj) { var result = [] for (let key in obj) { result.push(key) } return result } // 執行鏈式操做 _.chain = function (obj) { //數據源 var instance = _(obj) instance._chain = true //檢測對象是否支持鏈式調用 return instance } //輔助函數 將數據包裝爲underscore實例 var ret = function (instance, obj) { if (instance._chain) { instance.warp = obj return instance } return obj } _.map1 = function (obj) { obj.push('123', 'hello') return obj } // 關閉鏈式調用 返回數據自己 _.prototype.value = function () { return this.warp } _.each = function (arr, callback) { var i = 0 for (; i < arr.length; i++) { callback.call(arr, arr[i]) } //console.log(arr) } // 檢測靜態方法 name 存放在數組中 // 遍歷數組 給_.prototype進行註冊 _.mixin = function (obj) { _.each(_.functions(obj), function (key) { var func = obj[key] //console.log(key) _.prototype[key] = function () { //console.log(this.warp) //數據源 //console.log(arguments) //callback // 進行參數合併 var args = [this.warp] Array.prototype.push.apply(args, arguments) return ret(this, func.apply(this, args)) } }) } _.mixin(_) root._ = _ })(this)
調用:
console.log(_.unique([1,2,3,1,2,3,'a','A'],function (item) { // 過濾大小寫 return typeof item ==='string'?item.toLowerCase():item })) console.log(_([4,5,6,4,5,6,'b','B']).chain().unique(function (item) { // 過濾大小寫 return typeof item ==='string'?item.toLowerCase():item }).map1().value())