jQuery
我们都知道 jQuery 可以链式调用,比如:
我们写个简单的 demo 模拟链式调用:
- 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[num]];
- 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 之所以能实现链式调用,关键就在于通过 ,返回调用对象。再精简下 demo 就是:
- var jQuery = {
- eq: function(){
- console.log('调用 eq 方法');
- return this;
- },
- show: function(){
- console.log('调用 show 方法');
- return this;
- }
- }
- jQuery.eq().show();
在 underscore 中,默认不使用链式调用,但是如果你想使用链式调用,你可以通过 _.chain 函数实现:
- _.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]) 会返回一个对象:
该对象的原型上有着 underscore 的各种方法,我们可以直接调用这些方法。
但是问题在于原型上的这些方法并没有像 jQuery 一样,返回 this ,所以如果你调用了一次方法,就无法接着调用其他方法了……
写一个精简的 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) {
- 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
中的。
所以为了实现链式调用,我们还需要对上一篇 中的 _.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(_);
_.value
- console.log(_.chain([1, 2, 3]).push(4).shift());
其实会打印一个对象
可是我希望获得值是 [2, 3, 4] 呀!
所以,我们还需要提供一个 value 方法,当执行 value 方法的时候,就返回当前 _wrapped 的值。
- _.prototype.value = function() {
- return this._wrapped;
- };
此时调用方式为:
- var arr = _.chain([1, 2, 3]).push(4).shift().value();
结合,最终的 underscore 代码组织结构如下:
underscore 系列
underscore 系列目录地址:。
underscore 系列预计写八篇左右,重点介绍 underscore 中的代码架构、链式调用、内部函数、模板引擎等内容,旨在帮助大家阅读源码,以及写出自己的 undercore。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。