underscore 系列第二篇,講解 underscore 的鏈式調用css
本文接着上篇《underscore 系列之如何寫本身的 underscore》,閱讀本篇前,但願你已經閱讀了上一篇。node
咱們都知道 jQuery 能夠鏈式調用,好比:git
$("div").eq(0).css("width", "200px").show();複製代碼
咱們寫個簡單的 demo 模擬鏈式調用:github
function JQuery(selector) {
this.elements = [];
var nodeLists = document.getElementsByTagName(selector);
for (var i = 0; i < nodeLists.length; i++) {
this.elements.push(nodeLists[i]);
}
return this;
}
JQuery.prototype = {
eq: function(num){
this.elements = [this.elements[0]];
return this;
},
css: function(prop, val) {
this.elements.forEach(function(el){
el.style[prop] = val;
})
return this;
},
show: function() {
this.css('display', 'block');
return this;
}
}
window.$ = function(selector){
return new JQuery(selector)
}
$("div").eq(0).css("width", "200px").show();複製代碼
jQuery 之因此能實現鏈式調用,關鍵就在於經過 return this
,返回調用對象。再精簡下 demo 就是:架構
var jQuery = {
eq: function(){
console.log('調用 eq 方法');
return this;
},
show: function(){
console.log('調用 show 方法');
return this;
}
}
jQuery.eq().show();複製代碼
在 underscore 中,默認不使用鏈式調用,可是若是你想使用鏈式調用,你能夠經過 _.chain 函數實現:app
_.chain([1, 2, 3, 4])
.filter(function(num) { return num % 2 == 0; })
.map(function(num) { return num * num })
.value(); // [4, 16]複製代碼
咱們看看 _.chain 這個方法都作了什麼:函數
_.chain = function (obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};複製代碼
咱們以 [1, 2, 3] 爲例,_.chain([1, 2, 3]) 會返回一個對象:優化
{
_chain: true,
_wrapped: [1, 2, 3]
}複製代碼
該對象的原型上有着 underscore 的各類方法,咱們能夠直接調用這些方法。ui
可是問題在於原型上的這些方法並無像 jQuery 同樣,返回 this ,因此若是你調用了一次方法,就沒法接着調用其餘方法了……this
可是試想下,咱們將函數的返回值做爲參數再傳入 _.chain
函數中,不就能夠接着調用其餘方法了?
寫一個精簡的 Demo:
var _ = function(obj) {
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
_.chain = function (obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
_.prototype.push = function(num) {
this._wrapped.push(num);
return this._wrapped
}
_.prototype.shift = function(num) {
this._wrapped.shift()
return this._wrapped
}
var res = _.chain([1, 2, 3]).push(4);
// 將上一個函數的返回值,傳入 _.chain,而後再繼續調用其餘函數
var res2 = _.chain(res).shift();
console.log(res2); // [2, 3, 4]複製代碼
然而這也太複雜了吧,難道 chain 這個過程不能是自動化的嗎?若是我是開發者,我確定但願直接寫成:
_.chain([1, 2, 3]).push(4).shift()複製代碼
因此咱們再優化一下實現方式:
var _ = function(obj) {
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
var chainResult = function (instance, obj) {
return instance._chain ? _.chain(obj) : obj;
};
_.chain = function (obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
_.prototype.push = function(num) {
this._wrapped.push(num);
return chainResult(this, this._wrapped)
}
_.prototype.shift = function() {
this._wrapped.shift();
return chainResult(this, this._wrapped)
}
var res = _.chain([1, 2, 3]).push(4).shift();
console.log(res._wrapped);複製代碼
咱們在每一個函數中,都用 chainResult 將函數的返回值包裹一遍,再生成一個相似如下這種形式的對象:
{
_wrapped: some value,
_chain: true
}複製代碼
該對象的原型上有各類函數,而這些函數的返回值做爲參數傳入了 chainResult,該函數又會返回這樣一個對象,函數的返回值就保存在 _wrapped 中,這樣就實現了鏈式調用。
_.chain
鏈式調用原理就是這樣,但是這樣的話,咱們須要對每一個函數都進行修改呀……
幸運的是,在 underscore 中,全部的函數是掛載到 _
函數對象中的,_
.prototype 上的函數是經過 _.mixin
函數將 _
函數對象中的全部函數複製到 _.prototype
中的。
因此爲了實現鏈式調用,咱們還須要對上一篇《underscore 系列之如何寫本身的 underscore》 中的 _.mixin
方法進行必定修改:
// 修改前
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return func.apply(_, args);
};
});
return _;
};
_.mixin(_);複製代碼
// 修改後
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
var chainResult = function (instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return chainResult(this, func.apply(_, args));
};
});
return _;
};
_.mixin(_);複製代碼
根據上面的分析過程,咱們知道若是咱們打印:
console.log(_.chain([1, 2, 3]).push(4).shift());複製代碼
其實會打印一個對象 {_chain: true, _wrapped: [2, 3, 4] }
但是我但願得到值是 [2, 3, 4] 呀!
因此,咱們還須要提供一個 value 方法,當執行 value 方法的時候,就返回當前 _wrapped 的值。
_.prototype.value = function() {
return this._wrapped;
};複製代碼
此時調用方式爲:
var arr = _.chain([1, 2, 3]).push(4).shift().value();
console.log(arr) // [2, 3, 4]複製代碼
結合上一篇文章,最終的 underscore 代碼組織結構以下:
(function() {
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this || {};
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
if (typeof exports != 'undefined' && !exports.nodeType) {
if (typeof module != 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
_.VERSION = '0.2';
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var isArrayLike = function(collection) {
var length = collection.length;
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
_.each = function(obj, callback) {
var length, i = 0;
if (isArrayLike(obj)) {
length = obj.length;
for (; i < length; i++) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
} else {
for (i in obj) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
}
return obj;
}
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
/** * 在 _.mixin(_) 前添加本身定義的方法 */
_.reverse = function(string){
return string.split('').reverse().join('');
}
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return chainResult(this, func.apply(_, args));
};
});
return _;
};
_.mixin(_);
_.prototype.value = function () {
return this._wrapped;
};
})()複製代碼
underscore 系列目錄地址:github.com/mqyqingfeng…。
underscore 系列預計寫八篇左右,重點介紹 underscore 中的代碼架構、鏈式調用、內部函數、模板引擎等內容,旨在幫助你們閱讀源碼,以及寫出本身的 undercore。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎star,對做者也是一種鼓勵。