添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

链式调用在JavaScript语言中很常见,是一种非常有用的代码构建技巧,如jQuery、Promise等,都是使用的链式调用

对象链式调用通常有以下几种实现方式,但是本质上都是通过返回对象之后进行调用。

  • this的作用域链,jQuery的实现方式,通常链式调用都是采用这种方式。
  • 返回对象本身, 同this的区别就是显示返回链式对象。
  • 函数链式调用通常有以下几种实现方式,

  • 遍历调用函数组、利用遍历、按顺序调用函数元素
  • 利用函数的调用栈、例如koa的洋葱圈的链式调用
  • 闭包返回对象的方式实现,这种方式与柯里化有相似之处、例如reduce的链式调用
  • 我将JavaScript链式调用分为以上几类,欢迎大家补充一起学习、

    对象链式调用-基础

    要点就是 return this

    /* 简单的链式调用 */
    function Person (name, age) {
        this.name = name
        this.age = age
    Person.prototype = {
        info() {
            console.log(`我的名字是${this.name},我今年${this.age}岁`);
            return this
        start() {
            console.log('开始起床!');
            return this
        eat() {
            console.log('开始吃饭');
            return this
        work() {
            console.log('开始工作!');
            return this
        run() {
            console.log('下班啦!下班啦!');
            return this
    const person = new Person('rose', 18)
    person.info().start().eat().work().run()
    // 我的名字是rose,我今年18岁
    // 开始起床!
    // 开始吃饭
    // 开始工作!
    // 下班啦!下班啦!
    

    对象链式调用-高阶

  • return this
  • //首先定义构造函数 Person
    function Person(name) {
    	this.name = name;
    	//任务队列(使用队列的先进先出性质来模拟链式调用函数的执行顺序)
    	this.queue = [];
        let fn = () => {
            console.log('init 组要做的事情')
    		//next方法是 Person 原型上的方法,用于从任务队列中取出函数执行
    		this.next();
    	//函数入队
    	this.queue.push(fn);
        // 一定要添加定时器、将其放入函数队列中
    	setTimeout(() => {
    		this.next();
    	},0);
    	return this;
    //在Person的原型上实现eat、sleep、sleepFirst以及辅助方法next
    Person.prototype = {
    	eat(food) {
    		let fn = () => {
    			console.log('吃' + ' ' +food)
              	this.next();
    		this.queue.push(fn);
    		return this;
    	sleep(time) {
    		let fn = () => {
    			setTimeout(() => {
    				console.log('碎觉' + '' + time);
                	this.next();
    			},time*1000)
    		this.queue.push(fn);
    		return this;
    	sleepFirst(time) {
    		let fn = () => {
                setTimeout(() => {
                    console.log('等待' + '' + time);
                    this.next();
                },time*1000)
            //sleepFirst要优先执行,所以放到队列首部,
            this.queue.unshift(fn);
            return this;
    	next() {
    		//从队列首部取出一个函数
    		let fn = this.queue.shift();
    		fn && fn();//如果fn存在就执行fn
    new Person('Hank').sleep(1).sleepFirst(5).eat('晚饭')
    // 等待5
    // init 组要做的事情
    // 碎觉1
    // 吃晚饭
    

    如果不想要obj.fn(),这种调用方式,就将显示的调用,再封装一层、底层都是对象的链式调用

    function _add(num){
        this.sum = 0
        this.sum += num
        return this
    _add.prototype.add = function(num){
        this.sum += num
        return this
     function add(num){
         return new _add(num)
    let res = add(1).add(2).add(3)
    console.log(res.sum); //6
    

    对象链式调用-promise的异步调用原理

    function MyPromise (fn) {
        // 回调收集
        this.callbackList = []
        // 传递给Promise处理函数的resolve
        const resolve = (value) => {
            // 注意promise的then函数需要异步执行
            setTimeout(() => {
                // 保存 value
                this.data = value;
                // 把callbackList数组里的函数依次执行一遍
                this.callbackList.forEach(cb => cb(value))
            - fn 为用户传进来的函数
            - 执行用户传入的函数 
            - 并且把resolve方法交给用户执行
        fn(resolve)
    // 往构造函数的原型上挂载.then方法
    MyPromise.prototype.then = function (onReaolved) {
        // return 一个promise 实例
        return new MyPromise((resolve) => {
            // 往回调数组中插入回调
            this.callbackList.push(()=>{
                const response = onReaolved(this.data)
                // 判断是否是一个 MyPromise
                if(response instanceof MyPromise){
                    // resolve 的权力被交给了user promise
                    response.then(resolve)
                }else{
                    // 如果是普通值,直接resolve
                    // 依次执行callbackList里的函数 并且把值传递给callbackList
                    resolve(response)
    var p1 = new MyPromise((resolve, reject) => {
            console.log('p1')
            setTimeout(() => {
                resolve(1)
            }, 1000);
        }).then(res => {
            return new MyPromise((resolve, reject) => {
                setTimeout(() => {
                    resolve(res+1)
                }, 1000);
        }).then(res => {
            console.log(res); // 2
            return res+1;
    p1.then(res => {
        console.log(res);  // 3
    
  • 每一个then都会返回一个新的promise
  • 将传给 then 的函数和新 promise 的 resolve 一起 push 到前一个 promise 的 callbacks 数组中
  • 当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promise 的 resolve,让其状态变更,这又会依次调用新 promise 的 callbacks 数组里的方法,循环往复
  • 如果返回的结果是个 promise,则需要等它完成之后再触发新 promise 的 resolve,所以可以在其结果的 then 里调用新 promise 的 resolve
  • 函数的链式调用-递归调用

    遍历函数组进行函数链式调用,比较简单

    // 模拟一系列函数
    function fn1(ctx, next) {
        console.log('函数fn1执行...');
    function fn2(ctx, next) {
        console.log('函数fn2执行...');
    function fn3(ctx, next) {
        console.log('函数fn3执行...');
    let fns = [fn1, fn2, fn3];
    // 定义一个触发函数
    const trigger = (fns) => {
        fns.forEach(fn => {
            fn();
    // 执行触发,所有函数依次执行
    trigger(fns); //
    

    函数的链式调用-洋葱圈调用

    koa的链式调用的底层原理、其实是利用函数调用栈

    // 模拟一系列函数
    function fn1(ctx, next) {
        console.log(ctx, '函数fn1执行...'); // 打印顺序 1
        next();
        console.log(ctx, 'fn1 ending'); // 打印顺序 6
    
    
    
    
        
    
    function fn2(ctx, next) {
        console.log(ctx,'函数fn2执行...'); // 打印顺序 2
        next();
        console.log(ctx, 'fn2 ending'); // 打印顺序 5
    function fn3(ctx, next) {
        console.log(ctx, '函数fn3执行...'); // 打印顺序 3
        next();
        console.log(ctx, 'fn3 ending'); // 打印顺序 4
    function wrap(fns) {
        // 必然会返回一个函数...
        return (ctx) => {
            // 闭包保留fns数组的长度
            let l = fns.length;
            // 调用时从第一个函数开始
            return next(0);
            function next(i) {
                // 此时已经是最后一个函数了,因为已经没有下一个函数了,因此直接返回即可
                if (i === l) return;
                // 拿到相应的函数
                let fn = fns[i];
                // 执行当下函数,将参数透传过来,每个函数的next是一个函数,因此通过bind返
                // 回,留在每个函数内部调用,并保留参数,实现递归
                return fn(ctx, next.bind(null, (i + 1)));
    let arr = [fn1, fn2, fn3];
    // 组合后的函数
    let fn = wrap(arr);
    // 执行 并 传入ctx
    fn({ word: 'winter is comming!' });
    

    看👇🏻图观察调用栈

  • 每次调用next函数的时候、都回去调用下一个函数
  • 到栈顶时再一层一层退回来执行、看图更清晰

    函数的链式调用-组合(reduce)链式调用

    典型的利用闭包实现链式调用

    // 模拟几个函数
    function fn1(arg1) {
        // ...对arg1的操作逻辑
        console.log('fn1的参数:', arg1); 
        let arg = arg1 + 30;
        return arg;
    function fn2(arg2) {
        // ...对arg2的操作逻辑
        console.log('fn2的参数:', arg2);
        let arg = arg2 + 20;
        return arg;
    function fn3(arg3) {
        // ...对arg3的操作逻辑
        console.log('fn3的参数:', arg3);
        let arg = arg3 + 10;
        return arg;
    // 省略所有容错判断
    function compose(fns) {
        let l = fns.length;
        if (!l) throw new Error('至少得有一个函数呀...');
        // 一个,就直接返回这个函数...
        if (l === 1) return fns[0];
        // 数组迭代,返回一个函数,函数的实体为后一个函数执行的返回值作为前一个函
        // 数的参数,然后前一个函数执行,最终返回第一个函数的返回值
        return fns.reduce((a, b, i) => {
            return function c(...arg) {
                return a(b(...arg))
    let fns = [fn1, fn2, fn3];
    // 将函数组合,形成复杂函数
    let fn = compose(fns);
    // 执行
    let r = fn(10);
    console.log(r)
    // 执行过程打印
    // fn3的参数: 10
    // fn2的参数: 20
    // fn1的参数: 40
    // 70
    
  • 1、返回值fn是一个闭包、调用 fn(10)、此时的a = function c , b = fn3 参数 arg = 10,那么fn3(10) 返回值是 20 再传入a = function c( 10)
  • 2、此时 function c 又是一个闭包、在它的闭包环境下、a=fn1 b=fn2、arg = 20、所以调用 fn2(20)、 返回值是40、再传入 a = fn1(40)、即70
  • 3、最后因为a 是 fn1、调用fn1后 直接return 、所以最后返回值为70
  • 函数的链式调用-jQuery中的链式调用

    jQuery中的链式调用非常经典、这里以最基础的jQuery框架为例探查一下jQuery如何通过this实现的链式调用。

    function jQuery(selector){
        return new jQuery.fn.init(selector);
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        init: function(selector){
            this[0] = document.querySelector(selector);
            this.length = 1;
            return this;
        length: 3,
        size: function(){
            return this.length;
    jQuery.fn.init.prototype = jQuery.fn;
    var body = jQuery("body");
    
  • 首先这是一个最基本的类,通过实例化之后,实例共享原型上的方法
  • jQuery 的原型对象有一个init属性,这个属性才是真正的构造函数
  • 因为每个构造函数都一个原型对象,构造函数的实例对象,都可以使用原型对象中封装的属性和方法、所以通过init()创建出来的对象,都可以使用原型对象上的方法、jQuery的原型对象上有这些方法, 那么 jQuery.fn.init.prototype = jQuery.fn即可
  • 所以当调用jQuery("body")的时候,执行init函数、实例化一个对象,并且能够共享原型上的方法、并且返回这个对象
  • 即经典的 return this 链式调用
  • JS函数链式调用的几种方式 juejin.cn/post/716913…
  • 面试官必问系列之js的链式调用 codeleading.com/article/658…
  • JavaScript中的链式调用 www.cnblogs.com/WindrunnerM…
  • 链接:juejin.cn/post/684490…
  • zhuanlan.zhihu.com/p/110512501
  • juejin.cn/post/684490…
  • segmentfault.com/a/119000001…
  • github.com/songjinzhon…
  • leohxj.gitbooks.io/front-end-d…
  • developer.mozilla.org/zh-CN/docs/…
  • JavaScript
  • 私信