微信扫一扫看面试题

关注面试题库

JavaScript中链式调用大合集、应付面试够够的

 大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  https://mp.weixin.qq.com/s?__biz=MzU5NzA0NzQyNg==&mid=2247485824&idx=3&sn=70cd26a7c0c683de64802f6cb9835003&scene=21#wechat_redirect

 

链式调用在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 链式调用

 大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库  https://mp.weixin.qq.com/s?__biz=MzU5NzA0NzQyNg==&mid=2247485824&idx=3&sn=70cd26a7c0c683de64802f6cb9835003&scene=21#wechat_redirect

posted @ 2023-03-28 16:47  web前端面试小助手  阅读(52)  评论(0编辑  收藏  举报  来源