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,对做者也是一种鼓励。