js 链式调用

在开发的过程中,我们可能会用到 Promise,而使用 Promise,我们可能会链式调用其 then 方法,类似于

new Promise()
		.then(res1 => {console.log(res1)})
		.then(res2 => {console.log(res2)})
		.then(res3 => {console.log(res3)})
		...

那么链式调用是怎么实现的呢?

链式调用原理

每次方法执行之后返回 this 对象,这样后续的方法就可以继续在 this 环境下执行,也就是当前执行环境下执行

举个例子:

// 定义一个构造函数 Test
function Test(name) {
   this.name = name;
   console.log(this.name);
}
// 在 Test 的原型上定义一些方法
Test.prototype = {
	eat(food) {
		console.log(food)
		//return this
	}
	sleep(time) {
		console.log(time)
	}
}

new Test('hank').eat('lunch') // 测试代码1
new Test('hank').eat('lunch').eat('dinner').sleep(5) // 测试代码2
new Test('hank').eat('lunch').eat('dinner').sleep(5).eat('breakfast') // 测试代码3


上述例子中,测试代码 1 依次输出 hank lunch,这个没什么问题,因为我们实例化 Test 函数,执行 console.log(this.name),然后调用了其原型上的 eat 方法,eat 方法执行 console.log(food)。

那么测试代码 2 输出什么呢?是依次输出 hank lunch dinner 5 吗?

真实答案如下图,我们看到输出了 hank lunch,跟测试代码 1 一样,但是到第二次调用 eat 方法的时候,报错了,因为我们的 eat 方法没有返回值,没有返回值相当于在 eat 方法中 return undefined,那么我们再在后面继续调用 eat 方法时,就会得到如下错误,因为 undefined 上不存在 eat 方法。

在这里插入图片描述

那么怎样才能输出正确结果 hank lunch dinner 5?答案是在 eat 方法中 return this。这样一来,第一次调用 eat 方法之后,方法返回 this,还是指向当前 Test 实例,自然可以继续访问实例的 eat 方法,这样就可以一直调用 eat 方法。但是测试代码 3 中,最后一次调用 eat 方法会报错,因为 sleep 方法没有 return this,如果想调用成功,加上 return this 即可。

有执行顺序的链式调用

对于上述测试代码 2 ,如果面试官要求先执行 sleep 方法,要求如下:

new Test('hank').eat('lunch').eat('dinner').sleep(5)
// 依次输出 hank 5 lunch dinner

那么阁下如何应对?

思考一下,上述要求中,最后调用的方法却要先执行,也就是说 sleep 方法要优先执行,这就引入了优先级调度,实现这种调度我们第一个想到的数据结构就是队列,利用队列先进先出的特性,我们遇到 eat 方法时,把它依次放进队列中,当遇到 sleep 方法时,我们把它放到队头,这样后续在依次取出队列中的函数执行时,第一个取出执行的就是 sleep 方法。

根据上面分析,为了应对有执行顺序的链式调用,我们需要两个工具:一个是队列 queue,用来存放待执行的方法;一个是调度器,其实就是一个调度函数,用来取出 queue 中的方法并执行。

举个例子

new CodingMan('Hank').eat('breakfast').work('Coding').sleep(1).eat('lunch').work('Coding').eat('dinner').sleepFirst(5)
// 输出 Hi! this is Hank!
// 5 s 后输出 wake up after5(即先执行sleepFirst方法)
// 然后输出 Eat breakfast~
// 输出 Coding
// 输出 Eat lunch~
// 1s 后输出 wake up after1
// 输出 Coding
// 输出 Eat dinner~

上述例子模拟了程序员的一天,即一个叫 Hank 的程序员,先睡觉,5秒后醒来吃早餐,然后工作、吃午饭、午休、起床继续工作,最后吃晚餐。

下面是代码实现:

// 定义构造函数 CodingMan
function CodingMan(name) {
	this.name = name
	this.queue = [] // 队列,存放 CodingMan 的原型方法
	console.log('Hi! this is' + ' ' + this.name + '!')
	// 使用 setTimeout 触发一个宏任务
    setTimeout(() => {
        //next方法是 CoddngMan 原型上的方法,用于从任务队列中取出一个函数并执行
        this.next() // 调度函数
    },0);
}

// 在 CodingMan 的原型上定义方法(CodingMan 需要做的事情)

CodingMan.prototype = {
	eat(food) {
		let fn = () => {
			console.log('Eat' + ' ' +food + '~')
          	this.next()
		}
		this.queue.push(fn)
		
		return this
	},
	work(job) {
		let fn = () => {
			console.log(job)
			this.next()
		}
		this.queue.push(fn)
		
		return this
	},
	sleep(time) {
		let fn = () => {
			setTimeout(() => {
				console.log('wake up after' + '' + time)
            	this.next()
			}, time*1000)
		}
		this.queue.push(fn)
		
		return this
	},
	sleepFirst(time) {
		let fn = () => {
			setTimeout(() => {
				console.log('wake up after' + '' + time)
            	this.next()
			}, time*1000)
		}
		this.queue.unshift(fn) // 放入队头,优先执行
		
		return this
	},
	next() {
		let fn = this.queue.shift() // 从队头取出一个方法
		fn && fn() // 方法存在就执行
	}
}

观察上述代码,Codingman 原型上的每个方法最后都返回 this 对象,这是实现链式调用的核心。我们在 Codingman 构造函数中定义了一个队列 queue,然后使用 setTimeout 触发一个宏任务,setTimeout 的回调函数里调用了 Codingman 原型上的 next 方法,next() 就是我们前面提到的调度器,它只做一件事,那就是从队头取出一个函数,如果存在则执行。

Codingman 原型上的每个方法,把这个方法需要在控制台输出的东西放进函数 fn 中,然后 push 进 queue 中(this.queue.push(fn)),比如 eat 方法。由于 sleepFirst 方法需要优先执行,所以它的 fn 需要添加到队头(this.queue.unshift(fn))。

注意:每个 fn 中都需要调用一下 next 方法,这样才能保证 queue 中所有 fn 都能执行(第一次调用 next 方法是在构造函数的 setTimeout 中)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值