underscore做爲開發中比較經常使用的一個javascript工具庫,提供了一套豐富的函數式編程功能,該庫並無拓展原有的javascript原生對象,而是在自定義的
_
對象上,提供了100多個方法函數。在這個系列中,將從uderscore源碼角度, 打造一個本身的underscore框架javascript
現代js 庫的框架設計,通常都是以自執行函數的形式,自執行函數通常有兩種形式前端
(function(){ // 形式一 }())
(function(){ // 形式二 })()
咱們知道,函數聲明的形式會掛載到window對象做爲方法存在,而函數表達式的形式則會掛載在window對象做爲屬性存在,這都會形成變量污染,而自執行函數的好處在於能夠防止變量的污染,函數執行完後便馬上銷燬掉。java
underscore有兩種風格形式可使用,一種是面向對象類型,另外一種是函數類型。node
// 例子 _.map([1, 2, 3], function(n){ return n * 2; }); _([1, 2, 3]).map(function(n){ return n * 2; });
所以,在定義underscore類的時候須要考慮對象和函數兩種場景。當以函數的形式調用時須要把 _ 看成一個構造函數並返回他的實例化。代碼以下編程
(function(root){ var _ = function (obj) { if (!(this instanceof _)) { return new _(obj) } } root._ = _ }(this))
如今前端開發重視模塊化,以node服務端而論, 咱們有commonjs規範,以客戶端而論,咱們有AMD 和 CMD規範,對應的模塊加載器爲 requirejs 和 seajs。目前通行的javascript模塊規範主要集中在commonjs 和 AMD,所以爲了讓定義的underscore庫可以適用於各類規範。在框架的定義時需檢測使用環境併兼容各類規範。api
define('**', [], function(){ return '***' })
暴露模塊(function (root) { var _ = function (obj) { if (!(this instanceof _)) { return new _(obj) } } // commonjs 規範 檢測 module.exports 是否存在 if ((typeof module !== 'undefined' && module.exports)) { module.exports = { _: _ } } else { root._ = _;// window 對象暴露方法 } // amd 規範,檢測 define.amd 是否存在 if (typeof define == 'function' && define.amd) { define('underscore', [], function () { return _; }); } }(this))
// commonjs const _ = require('./underscore.js') console.log(_)
// AMD require(['underscore'], function (underscore) { console.log(underscore) })
underscore的調用,既能夠經過_.unique()
,也能夠經過 _().unique()
,兩種方法效果相同卻須要在框架設計時定義兩套方法,一套是定義 _ 對象的靜態方法,另外一套是擴展 _對象原型鏈上的方法。數組
_.uniqe = function() {} _.prototype.unique = function() {}
爲了不冗餘代碼,能夠將定義好的靜態方法複製一份成爲原型鏈上的方法app
(function(root){ ··· _.mixins = function() { // 複製靜態方法到原型上 } _.mixins() // 執行方法 }(this))
mixins 方法的實現,須要遍歷 underscore 對象上全部的靜態方法,所以須要先完成對 遍歷方法 _.each 的實現框架
_.each(list, iteratee, [context]) Alias: forEach
遍歷list中的全部元素,按順序用每一個元素當作參數調用 iteratee 函數。若是傳遞了context參數,則把iteratee綁定到context對象上。每次調用iteratee都會傳遞三個參數:(element, index, list)。若是list是個JavaScript對象,iteratee的參數是 (value, key, list))。返回list以方便鏈式調用。模塊化
each 的第一個參數按照文檔能夠支持 數組,類數組,對象三種類型,數組類數組和對象在遍歷時的處理方式不一樣。前者回調函數處理的是 值和下標,後者處理的是 值和屬性。
// 判斷數組,類數組方法 (function(root) { ··· _.each = function (list, callback, context) { // context 存在會改變callback 中this 的指向 var i = 0; var key; if (isArrayLikeLike(list)) { // 數組,類數組 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) { // 返回參數 collection 的 length 屬性值 var length = collection.length; // length是數值,非負,且小於等於MAX_ARRAY_INDEX // MAX_ARRAY_INDEX = Math.pow(2, 53) - 1 return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; } }(this))
_.mixin({ capitalize: function(string) { return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); } }); _("fabio").capitalize(); => "Fabio"
固然也能夠用來內部拷貝靜態方法到原型鏈的方法上。
(function(root){ ··· var push = Array.prototype.push var _ = function (obj) { if (!(this instanceof _)) { return new _(obj) } this.wrap = obj // 存儲實例對象傳過來的參數 } _.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))
其中關注點在arguments 的處理上,靜態方法須要傳遞目標源做爲方法的參數 即_.unique(目標源, 回調函數)
,而實例方法的目標源存儲在構造對象的屬性中 ,即_(目標源).unique(回調函數)
,所以定義實例方法時須要合併屬性和回調函數。即Array.prorotype.push.apply([this.wrap], arguments)
,以後將他做爲參數傳遞給靜態方法並返回處理結果。
將類數組轉成數組的方法
Array.prototype.slice.call(類數組)
var a = []; Array.prototype.push.apply(a, 類數組); console.log(a);
var a = []; Array.prototype.concat.apply(a, 類數組); console.log(a);
Array.from(類數組)
var args = [...類數組]
返回一個封裝的對象. 在封裝的對象上調用方法會返回封裝的對象自己, 直道 value 方法調用爲止。
underscore中方法的調用返回的是處理後的值,所以沒法支持方法的鏈式調用。若是須要鏈式調用,須要使用chain()方法,chain的使用會使每次調用方法後返回underscore的實例對象,直到 調用value方法才中止返回。
(function(root){ ··· // chain方法會返回 _ 實例,而且標註該實例是否容許鏈式調用的 _.chain = function(obj) { var instance = _(obj); instance.chain = true; return instance } // 增長是否支持鏈式調用的判斷,若是支持,則返回該實例,不支持則直接返回結果, 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)) // 修改實例方法的返回值,返回值經過chainResult 包裝,根據chainResult的判斷結果改變返回值 } }) } }(this))
由於鏈式調用會使underscore的方法返回他的實例對象,因此當須要結束這一調用行爲時,須要使用value()。 value()方法會返回調用的結果。
(function(root){ ··· _.value = function(instance) { return instance.wrap } }(this))