underscore 系列之链式调用

简介: underscore 系列第二篇,讲解 underscore 的链式调用

1.png


underscore 系列第二篇,讲解 underscore 的链式调用


前言


本文接着上篇《underscore 系列之如何写自己的 underscore》,阅读本篇前,希望你已经阅读了上一篇。


jQuery


我们都知道 jQuery 可以链式调用,比如:


$("div").eq(0).css("width", "200px").show();复制代码


我们写个简单的 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[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();复制代码


_.chain


在 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]) 会返回一个对象:


{
    _chain: true,
    _wrapped: [1, 2, 3]
}复制代码


该对象的原型上有着 underscore 的各种方法,我们可以直接调用这些方法。


但是问题在于原型上的这些方法并没有像 jQuery 一样,返回 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(_);复制代码


_.value


根据上面的分析过程,我们知道如果我们打印:


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 系列


underscore 系列目录地址:github.com/mqyqingfeng…


underscore 系列预计写八篇左右,重点介绍 underscore 中的代码架构、链式调用、内部函数、模板引擎等内容,旨在帮助大家阅读源码,以及写出自己的 undercore。


如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。



目录
相关文章
|
27天前
|
JavaScript API
优雅的setup(函数式)写法+封装
【10月更文挑战第15天】优雅的 `setup` 写法和封装可以让我们更好地组织和管理组件的逻辑,提高开发效率和代码质量。你可以根据自己的项目需求,不断探索和实践更合适的封装方式。
38 2
|
6月前
|
前端开发
Promise链式调用与错误处理
Promise链式调用是处理异步操作的方式,它按顺序执行多个任务,每个任务返回Promise对象。通过`.then()`指定成功后的操作,`.catch()`处理错误。示例代码展示了如何使用fetch获取数据,根据状态码解析响应并处理数据,错误则通过`.catch()`捕获。另一个例子定义了三个异步函数构成Promise链,依次执行并处理结果,错误同样由`.catch()`统一管理。
|
6月前
|
前端开发 JavaScript
Promise的链式调用案例讲解
Promise的链式调用案例讲解
|
6月前
|
前端开发
Promise的链式调用
Promise的链式调用是指在一个Promise对象上连续调用多个then方法的过程。通过链式调用,可以将多个异步操作按照顺序执行,并且可以在每个操作完成后处理返回的结果。
64 0
|
前端开发
8 # 链式调用
8 # 链式调用
42 0
|
前端开发 程序员
promise的链式调用和promise的嵌套的实现
promise的链式调用和promise的嵌套的实现
188 0
|
存储 SQL JavaScript
js数组高阶函数——filter()方法
js数组高阶函数——filter()方法
147 0
|
JavaScript
js 外部调用 嵌套函数
js 外部调用 嵌套函数
77 0
js 外部调用 嵌套函数
lodash函数包装
lodash函数包装
103 0
lodash函数包装
Lodash学习之集合调用函数处理
Lodash学习之集合调用函数处理
97 0
Lodash学习之集合调用函数处理