先過濾掉underscore內部各個工具函數的具體邏輯,只看源碼庫本身有什么內容。
構造函數
underscore有兩種調用方式:
- 風格對象
_.map([1, 2, 3], function(n){ return n * 2; });
- 函數風格
_([1, 2, 3]).map(function(n){ return n * 2; });
_
是一個函數對象,api中的函數全都掛載到_
上,實現_.func
// 使用立即執行函數
(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._=_;
})();
mixin
上一步中,我們創建了underscore實例,只能支持_.func
調用,如果要支持_(obj).func
,同時還要將func
注冊在實例的prototype
上。試想下,如果每聲明一個函數,就要綁定一次,那得用多…
在underscore中,使用mixin
將自定義函數添加到Underscore對象。
// 用于返回排序后的數組,包含所有的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
上,提高了代碼的復用度。
鏈式調用
類似于 Java Stream 流式編程
1584758293868.png
在Javascript中,數據可以像是在管道中流通,我們稱之為,聲明式編程/鏈接式調用。
data.filter(...).unique(...).map(...)
既然要滿足鏈接式調用(Chaining)語法,需要滿足兩個條件
- 前一次調用返回數據對象,用來給下一個函數調用提供數據來源
- 返回調用當前函數的對象,以保證可以調用下個函數
可能會想到,在每個函數結尾return 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())