先過濾掉underscore內(nèi)部各個工具函數(shù)的具體邏輯,只看源碼庫本身有什么內(nèi)容。
構(gòu)造函數(shù)
underscore有兩種調(diào)用方式:
- 風(fēng)格對象
_.map([1, 2, 3], function(n){ return n * 2; });
- 函數(shù)風(fēng)格
_([1, 2, 3]).map(function(n){ return n * 2; });
_
是一個函數(shù)對象,api中的函數(shù)全都掛載到_
上,實(shí)現(xiàn)_.func
// 使用立即執(zhí)行函數(shù)
(function () {
// 定義全局對象
var root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
// 省略...
// 創(chuàng)建_對象
var _ = function(obj) {
// 如果參數(shù)是underscore的一個實(shí)例,就直接返回該參數(shù)
if (obj instanceof _) return obj;
// 對應(yīng)_(xxx)調(diào)用,如果new實(shí)例不是_的實(shí)例,就返回實(shí)例化_對象
if (!(this instanceof _)) return new _(obj);
// 并將數(shù)據(jù)對象包裝在實(shí)例對象上
this._wrapped = obj;
};
// 省略...
//注冊在全局對象
root._=_;
})();
mixin
上一步中,我們創(chuàng)建了underscore實(shí)例,只能支持_.func
調(diào)用,如果要支持_(obj).func
,同時還要將func
注冊在實(shí)例的prototype
上。試想下,如果每聲明一個函數(shù),就要綁定一次,那得用多…
在underscore中,使用mixin
將自定義函數(shù)添加到Underscore對象。
// 用于返回排序后的數(shù)組,包含所有的obj中的函數(shù)名。
_.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的函數(shù),綁定在原型上
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
// this._wrapped作為第一個參數(shù)傳遞,其他用戶傳遞的參數(shù)放在后面。
var args = [this._wrapped];
push.apply(args, arguments);
return chainResult(this, func.apply(_, args));
};
});
return _;
};
// 執(zhí)行混入
_.mixin(_);
大致流程是:
- 獲取當(dāng)前實(shí)例上注冊的函數(shù)數(shù)組
- 遍歷數(shù)組將函數(shù)注冊在實(shí)例原型上
- 將
_(args).func(argument)
參數(shù)進(jìn)行合并
_
自身定義一系列函數(shù),通過_.mixin()
綁定在了_.prototype
上,提高了代碼的復(fù)用度。
鏈?zhǔn)秸{(diào)用
類似于 Java Stream 流式編程
在Javascript中,數(shù)據(jù)可以像是在管道中流通,我們稱之為,聲明式編程/鏈接式調(diào)用。
data.filter(...).unique(...).map(...)
既然要滿足鏈接式調(diào)用(Chaining)語法,需要滿足兩個條件
- 前一次調(diào)用返回數(shù)據(jù)對象,用來給下一個函數(shù)調(diào)用提供數(shù)據(jù)來源
- 返回調(diào)用當(dāng)前函數(shù)的對象,以保證可以調(diào)用下個函數(shù)
可能會想到,在每個函數(shù)結(jié)尾return this;
,對,能用但是沒必要,除了要手動添加外,我們也無法關(guān)閉鏈?zhǔn)健?/p>
在underscore中,使用的是可選式實(shí)現(xiàn)鏈接編程,使用.chain()
來開啟鏈?zhǔn)秸{(diào)用,然后使用.value()
獲取函數(shù)的最終值。
關(guān)鍵函數(shù):
// 開啟鏈?zhǔn)秸{(diào)用
_.chain = function (obj) {
//返回一個封裝的對象. 在封裝的對象上調(diào)用方法會返回封裝的對象本身
var instance = _(obj)
instance._chain = true //檢測對象是否支持鏈?zhǔn)秸{(diào)用
return instance
}
// 輔助函數(shù):鏈?zhǔn)街修D(zhuǎn)
// 鏈?zhǔn)秸{(diào)用 將數(shù)據(jù)對象封裝在underscore實(shí)例中
// 非鏈?zhǔn)秸{(diào)用 返回數(shù)據(jù)對象
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;
};
總結(jié)
此次學(xué)習(xí)目標(biāo)不是為了學(xué)習(xí)api,而是通過將其設(shè)計思想轉(zhuǎn)換為自己的。通過以上幾點(diǎn),我們可以大概實(shí)現(xiàn)一個簡化版的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
}
// 獲取對象上的函數(shù)
_.functions = function (obj) {
var result = []
for (let key in obj) {
result.push(key)
}
return result
}
// 執(zhí)行鏈?zhǔn)讲僮? _.chain = function (obj) { //數(shù)據(jù)源
var instance = _(obj)
instance._chain = true //檢測對象是否支持鏈?zhǔn)秸{(diào)用
return instance
}
//輔助函數(shù) 將數(shù)據(jù)包裝為underscore實(shí)例
var ret = function (instance, obj) {
if (instance._chain) {
instance.warp = obj
return instance
}
return obj
}
_.map1 = function (obj) {
obj.push('123', 'hello')
return obj
}
// 關(guān)閉鏈?zhǔn)秸{(diào)用 返回數(shù)據(jù)本身
_.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)
}
// 檢測靜態(tài)方法 name 存放在數(shù)組中
// 遍歷數(shù)組 給_.prototype進(jìn)行注冊
_.mixin = function (obj) {
_.each(_.functions(obj), function (key) {
var func = obj[key]
//console.log(key)
_.prototype[key] = function () {
//console.log(this.warp) //數(shù)據(jù)源
//console.log(arguments) //callback
// 進(jìn)行參數(shù)合并
var args = [this.warp]
Array.prototype.push.apply(args, arguments)
return ret(this, func.apply(this, args))
}
})
}
_.mixin(_)
root._ = _
})(this)
調(diào)用:
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())