大厂面试题分享 面试题库
前后端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库 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