来聊一道前端面试题吧

前言

金三银四,技术论坛上诸如:阿里、头条、腾讯….面经层出不穷,朋友圈不少小伙伴都在找工做也遇到了各类各样的麻烦。本文但愿那些在准备面试的过程当中蕉绿的童鞋别僵化了本身的思惟,以本身曾经碰见到一道面试题为引,用本身对待问题的想法行文,天马行空,从僵硬的知识点中跳脱出来一块儿思考,内容简单易懂。评论区有不少的同窗留下了许多很棒的思路,你们不要错过哟,欢迎你们一块儿继续交流学习。javascript

const fucArr = [
	next => {
		setTimeout(() => {
			console.log(1);
			next()
		}, 300)
	},
	next => {
		setTimeout(() => {
			console.log(2);
			next()
		}, 200)
	},
	next => {
		setTimeout(() => {
			console.log(3);
			next()
		}, 100)
	}
]

var run = arr=>{

  

}
// 实现一个run方法,使得run(fucArr)能顺序输出一、二、3.

复制代码

题目简析

咱们观察 fucArr 每个子项都具备以下结构:前端

  1. 接收一个方法 next
  2. 有一个计时器,计时器回调方法体内对应着相应的输出
  3. 输出结束调用 next 方法。

他们的差别就是:计时器时间逐个减小。java

直接循环调用 3 个方法确定是不可取的。为了能按序输出,函数的执行过程应该是上一个函数 console 以后, 再执行下一个函数,而接收的这个 next参数就是执行下一个方法的关键。由于是头到尾依次调用,咱们就把fucArr 称之为一个队列。面试

思路1、

咱们假象本身是个编译器,而后把执行的过程进行单步拆解。express

  1. fucArr 是作先执行等待队列第一个,等待中的函数队列为原函数队列的slice(1);
  2. 等待next执行后,而后又执行等待函数队列的第一个函数,等待中的函数队列为原函数队列的slice(1);

听着是否是很像一个递归的过程,没错,那咱们先用递归来实现一下redux

var run = arr => {
	// 递归语句千万条,找到出口第一条,那我们判断递归出口的条件就是等待队列为空
	if (arr.length === 0) return;
	// 好的,一句话执行过程写完了
	arr[0](() => run(arr.slice(1)));
}
run(fucArr)

// 1 2 3;
复制代码

思路2、

如今咱们从递归的思路中跳脱出来,换种思路继续思考.....闭包

上一个函数执行到某个时机触发了下一个函数的执行。app

也就是说上一个函数 trigger,下一个函数才开始执行。koa

根据描述 trigger 实际上作的就是触发等待队列的第一个函数的执行,所以咱们能够以下定义。函数

var run = arr => {
	const trigger = () => {
		if (arr.length === 0) return;
		arr.shift()();
	}
}
复制代码

那么 trigger 什么时候进行调用呢?很显然, 上一个函数式经过next 去触发下一个函数调用,所以 trigger 应该就是函数接收的next,咱们为了方便参数绑定须要重构一下我们的等待队列函数。固然不要忘了,首次执行要手动trigger一下喔。

var run = arr => {
	const trigger = () => {
		if (arr.length === 0) return;
		arr.shift()();
	}
	arr = arr.map(val => {
		return () => val(trigger);
	})
	trigger();
}
复制代码

其实作参数绑定还有一种更优雅一点的方式,bind,因此你们注意咯,bind不仅仅能绑定this喔。

咱们能够稍微改动一下:

var run = arr => {
	const trigger = () => {
		if (arr.length === 0) return;
		arr.shift()();
	}
	arr = arr.map(val => {
		return val.bind(null, trigger);
	})
	trigger();
}
复制代码

都9102年了,既然是前端面试那确定少不了Promise 的对吧,那咱们可不能够掺入一些Promise的元素在里面呢?答案是必然的。

根据Promise的特性,当自己状态改变,去触发then里的方法(这里不要深究这句话,意思了解就好)。是resolve 做为自己状态改动的方法。那状态改变是去作什么事呢?好的,没错trigger。那什么时候状态改变呢?上一个函数next调用的时候。

var run = arr => {
	const trigger = () => {
		if (arr.length === 0) return;
		arr.shift()();
	}
	arr = arr.map(val => {
		return () => new Promise(resolve => {
			val(resolve)
		}).then(trigger);
	})
	trigger();
}
复制代码

redux的思路、

如今继续清空上面的思路,不要被干扰。

首先给 applymiddleware(如下简称amw)一个简单的定义,amw是接收若干个函数做为参数,最终会返回一个函数,这个函数调用,会按照顺序,依次执行前面做为参数传入的函数。为了避免把问题复杂化,请接收个人误导引导,不要怀疑。

如下是做为参数传入的函数要求的结构如下称a结构:

store=>next=>action=>{
	// dosomething...
	next()
}
复制代码

a结构在第一次调用时,会返回一个方法,第二次调用时返回第二个方法,咱们先来看源码的一个操做过程。

const chain = middlewares.map(middleware => middleware(middlewareAPI))
复制代码

首先是一层循环调用,使得函数体变为b结构:

next=>action=>{
	// dosomething...
	next()
}
复制代码

这样作是为了以闭包的形式在 dosomething 中可以使用到 middlewareApi

根据b结构咱们能够稍稍改变下原题 :

const fucArr = [
	next => action => {
		setTimeout(() => {
			console.log(action++);
			next(action)
		}, 300)
	},
	next => action => {
		setTimeout(() => {
			console.log(action++);
			next(action)
		}, 200)
	},
	next => action => {
		setTimeout(() => {
			console.log(action++);
			next(action)
		}, 100)
	}
]

var run = arr=>{

  

}
// 实现一个run方法,run方法接收fucArr为参数;返回一个函数,这个函数接收一个参数1,最终,依次输出一、二、3
// run(fucArr)(1) => 1 2 3
复制代码

变题相对于多了一个参数传递的过程,实际上咱们须要顺序执行的实际上是结构c:

action=>{
    // dosomething...
	next()
}
复制代码

这些关键仍是要如何构建每一个函数接收的参数next

咱们作以下假设,当fucArr只有一个函数时 返回的就应该是:

fucArr[0](()=>{}) // 为了不报错,next应为一个空函数
// 即:
action => {
    setTimeout(() => {
        console.log(action++);
        //(()=>{}) 这玩意儿就是接收的next
        (()=>{})(action)
    }, 300)
}
复制代码

fucArr有两个函数时返回:

fucArr[0](fucArr[1](()=>{}))
// 即:
action => {
    setTimeout(() => {
        console.log(action++);        
		fucArr[1](()=>{})(action)
    }, 300)
}
复制代码

当有三个函数的时返回:

fucArr[0](fucArr[1](fucArr[2](()=>{}))
复制代码

仔细观察返回函数的结构发现,全部的函数都是接受上一个函数调用后的返回值(如下称模式1),最后一个函数接收的是一个空函数。咱们尝试构建模式1:

// 首先初始想法模型是这样的
// 可是因为我们是程序执行,不能像上面我们描述问题的时候,继续往next里塞函数。
// 而在遍历到 next 的下一个函数的时当前是没法明确next应该是什么,所以咱们须要将模式改变一下。
pre(next());
// 当遍历到next下一个节点时,把当前函数做为arg传入进来
arg=>pre(next(arg))
复制代码

pre + next + 遍历,这三个关键词没错,就是reduce。所以:

var reduceResult = fucArr.reduce((pre,next)=> (...arg)=>pre(next(...arg)));
// 咱们发现这个返回的仍是一个 arg=>pre(next(arg)) 这样模式的函数,接收的参数任然是一个函数。
// 因而乎真的须要返回的函数实际上是 
return reduceResult(()=>{});
复制代码

因此最终形态是

var run = arr=>{
	var reduceResult = arr.reduce((pre,next)=> (...arg)=>pre(next(...arg)));
	return reduceResult(()=>{});
}
run(fucArr)(1);
// 1 2 3
复制代码

总结

其实还能够聊下expresskoa中间件compose的思路,可是没有必要(汪汪大笑.gif)。本文主旨也不是灌输这个题目的解法,只是但愿你们未来在面试和工做中遇到问题尝试着用本身构建的知识体系去解决积极面对,最后祝小伙伴们找工做顺利。

相关文章
相关标签/搜索