说在前面

  说实话,刚开始在听到这个面试题的时候,我是诧异的,红绿灯?这不是单片机、FPGA、F28335、PLC的实验吗?!

  而且还要用Promise去写,当时我确实没思路,只好硬着头皮去写,下来再review的时候,才真正懂了Promise红绿灯的实现原理

  下来我就由浅至深的分析Promise红绿灯的实现原理

  下面我就不讲promise的原理和特点了,想具体看了解的可以看阮一峰老师的教程

  主要说下红绿灯用到promise最核心的一点就是  “promise实例的状态变为Resolved,就会触发then方法绑定的回调函数

  我是在做这个demo途中才彻底理解了这句话的真正含义。

简单实现

  用文字绿灯、黄灯、红灯来模拟表示红绿灯

function timeout(){
return new Promise(function(resolve,reject){
setTimeout(resolve,1000,"绿灯");
}
function timeout2(){
return new Promise(function(resolve,reject){
setTimeout(resolve,2000,"黄灯");
})
}
function timeout3(){
return new Promise(function(resolve,reject){
setTimeout(resolve,3000,"红灯");
})
}
(function restart(){
timeout().then((value)=>{
console.log(value);
})
timeout2().then((value)=>{
console.log(value);
})
timeout3().then((value)=>{
console.log(value);
restart();
})
})()

  建立三个promise对象,分别用timeout1 timeout2 timeout3 包起来,promise对象里面含有定时器setTimeout,以连续的1000-》2000-》3000的时间表示每次灯亮的时间的为1秒

  下面是实现的demo效果


  这种实现有一个问题,如果设定绿灯是5000ms,黄灯是2000ms,红灯是3000ms,
  则会出现先显示黄灯,后显示红灯,显示绿灯的同时也会同时显示黄灯,
  因为第二轮绿灯的5000ms包含了黄灯的2000ms
  这就不符合红绿灯的思想与逻辑
较复杂实现

  针对上一个问题,所以有了第二种解决方案

function green(){
return new Promise(function(resolve,reject){
console.log("绿灯"+new Date().getSeconds())
resolve();
})
}
function yellow(){
return new Promise(function(resolve,reject){
console.log("黄灯"+new Date().getSeconds())
resolve();
})
}
function red(){
return new Promise(function(resolve,reject){
console.log("红灯"+new Date().getSeconds())
resolve();
})
}
function ms_5000(){
return new Promise(function(resolve,reject){
setTimeout(resolve,5000)
})
}
function ms_3000(){
return new Promise(function(resolve,reject){
setTimeout(resolve,3000)
})
}
function ms_2000(){
return new Promise(function(resolve,reject){
setTimeout(resolve,2000)
})
}
(function restart(){
green()
.then(ms_5000) //绿灯显示5s转红灯
.then(yellow)
.then(ms_3000) //黄灯显示3s转红灯
.then(red)
.then(ms_2000) //红灯显示2s转绿灯
.then(arguments.callee)
})()

  建立三个promise对象 分别用green yellow red  函数包起来,并返回promise对象的resolve,promise对象的状态变成Resolved 也就是说return了reslove就可以可以触发then方法绑定的回调函数

  又建立了三个定时器,用于延时,三个定时器中用到了resolve函数,resolve是js引擎自带的函数,也表示promise的状态变成了Resolved,可以触发then方法绑定的回调函数。

  实现的demo如下,demo的数字是时间戳,当前的秒数,绿灯55 黄灯0    表示绿灯执行5秒后转到黄灯,下面的同理

  但是这样做还是有点麻烦,代码复用率低,要建立3个promise对象,3个定时器,无疑是消耗内存的。

  倒数第2行 arguments.callee的含义下面也会解释。

  

较复杂实现(理理思路)
function green(){
return new Promise(function(resolve,reject){
console.log("绿灯当前秒数"+new Date().getSeconds())
resolve();
})
}
(function restart(){
green().then(function(){
return new Promise(function(resolve,reject){
setTimeout(resolve,5000);
})
}).then(function(){
return new Promise(function(resolve,reject){
console.log("黄灯当前秒数" + new Date().getSeconds())
resolve();
})
}).then(function(){
return new Promise(function(resolve,reject){
setTimeout(resolve,3000);
})
}).then(function(){
return new Promise(function(resolve,reject){
console.log("绿灯当前秒数"+ new Date().getSeconds())
resolve();
})
}).then(function(){
return new Promise(function(resolve,reject){
setTimeout(resolve,2000)
})
}).then(arguments.callee);
})()

  上述的代码功能和第2点相同,只是为了理理思路,体现出promise的状态变成Resolved时,可以触发then方法绑定的回调函数

  就像上述代码所示,执行红绿灯的显示,每次都会返回resolve,或者定时器也会使用resolve函数,表示promise的状态确实变成Resolved了。promise有三种状态pending fullfilled rejected ,pending到fulfilled表示的就是Resolved。

  demo如下

  

完美实现(实现架构)

  正如上面所说,上述的方法要建立3个promise对象,代码复用率低,那有没有更加严(gao)格(duan)的的方法,答案是有的,但是在看写代码前需要理理思路,分析一下代码的架构如何去写

function green2yellow2red(){
return function(){
// someCode
return new Promise(function(){
// someCode
})
}
}
var green = green2red2yellow(setTimeout).bind(null, 3000);
var yellow = green2red2yellow(setTimeout).bind(null, 4000);
var red = green2red2yellow(setTimeout).bind(null, 5000); (function(){
// IIFE
green()
})()

  上述代码使用一个promise对象,用green2yellow2red函数包起来,有两个return,第一个return是为了给第二个return的promise对象bind延迟时间,bind绑定的参数可以通过arguments访问到,必须是第一个return的函数中的arguments,arguments是什么下面也会讲。

  下面打印好多参数,下面我详细解释一下他们的区别

function green2red2yellow(){
console.log(arguments)
// [ƒ, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// [ƒ, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// [ƒ, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(this);
// Window {stop: ƒ, open: ƒ, alert: ƒ, confirm: ƒ, prompt: ƒ, …}
// Window {stop: ƒ, open: ƒ, alert: ƒ, confirm: ƒ, prompt: ƒ, …}
// Window {stop: ƒ, open: ƒ, alert: ƒ, confirm: ƒ, prompt: ƒ, …}
return function(){
console.log(arguments)
// [3000, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(this);
// Window {stop: ƒ, open: ƒ, alert: ƒ, confirm: ƒ, prompt: ƒ, …}
console.log(arguments.callee.length)
// 形参的个数
console.log(arguments.length)
// 实参的个数
var arr = [];
var arr2 =[].slice.call(arguments); //把arguments类数组转成真数组
console.log(arguments[0]) //3000 type是Number
console.log(arr.push(arguments)) //返回1表示当前代码执行结果为真
console.log(arr); //[Arguments(1)]
console.log(arr2) // [3000] type是Array
return new Promise(function(){
console.log(arguments)
// (2)[ƒ, ƒ, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 实参的类数组
})
}
}
var green = green2red2yellow(setTimeout).bind(null, 3000);
var yellow = green2red2yellow(setTimeout).bind(null, 4000);
var red = green2red2yellow(setTimeout).bind(null, 5000); (function(){
// IIFE
green()
})()
//测试代码段
var promise = new Promise(function(){
console.log(arguments)
// (2)[ƒ, ƒ, callee: ƒ, Symbol(Symbol.iterator): ƒ]
})

  在green2red2yellow函数中直接console.log(arguments),打印出来三个数组,是因为实例了三次promise对象,分别是green,yellow,red,三次都指向同一个对象,所以打印了三次。

  在green2red2yellow函数中的第一个return中console.log(arguments),只打印在IIFE中执行的的promise对象,就是green对象

  在green2red2yellow函数中的第二个return中console.log(arguments),显示结果前面有一个2表示,实参的个数是2

  在测试代码段中测试了一下,确实是。

  arguments:以类数组的方式存放着当前对象的实参。green2red2yellow函数中访问是green2red2yellow这个函数对象,green2red2yellow函数中第一个return中访问是return的function  bind了参数的的对象,green2red2yellow函数中第二个return是promise对象

  arguments.callee:正在执行的这个函数的引用

  arguments.callee.length:当前对象形参的个数

  arguments.length:当前对象实参的个数

  如何把arguments这个类数组转换成数组呢:[ ].slice.call(arguments) 这是最稳妥的方法

  下面的使用两种方式把arguments转成数组及两者的区别

        var arr = [];
var arr2 =[].slice.call(arguments); //把arguments类数组转成真数组
console.log(arguments[0]) //3000 type是Number
console.log(arr.push(arguments)) //返回1表示当前代码执行结果为真
console.log(arr); //[Arguments(1)]
console.log(arr2) // [3000] type是Array

  

  由此可知 [ ].slice.call(arguments)是最稳妥的方式

完美实现

  下面写出我觉得最完美的的实现方式

  html:

    <ul id="traffic" class="">
<li id="green"></li>
<li id="yellow"></li>
<li id="red"></li>
</ul>

  css:

/*垂直居中*/
ul {position: absolute;width: 200px;height: 200px;top: 50%;left: 50%;transform: translate(-50%,-50%);}
/*画3个圆代表红绿灯*/
ul >li {width: 40px;height: 40px;border-radius:50%;opacity: 0.2;display: inline-block;}
/*执行时改变透明度*/
ul.red >#red, ul.green >#green,ul.yellow >#yellow{opacity: 1.0;}
/*红绿灯的三个颜色*/
#red {background: red;}
#yellow {background: yellow;}
#green {background: green;}

  JS:

function green2red2yellow(timer){
return function(){
var arr = [].slice.apply(arguments)
// var self = this;
return new Promise(function(resolve,reject){
arr.unshift(resolve)
timer.apply(self,arr);
})
}
}
var green = green2red2yellow(setTimeout).bind(null, 3000);
var yellow = green2red2yellow(setTimeout).bind(null, 4000);
var red = green2red2yellow(setTimeout).bind(null, 5000);
var traffic = document.getElementById("traffic");
(function restart(){
'use strict' //严格模式
console.log("绿灯"+new Date().getSeconds()) //绿灯执行三秒
traffic.className = 'green'; green()
.then(function(){
console.log("黄灯"+new Date().getSeconds()) //黄灯执行四秒
traffic.className = 'yellow';
return yellow();
})
.then(function(){
console.log("红灯"+new Date().getSeconds()) //红灯执行五秒
traffic.className = 'red';
return red();
}).then(function(){
restart()
})
})();
1、var arr = [].slice.apply(arguments)  
表示把arguments转成数组
2、arr.unshift(resolve)
unshift或shift 在数组首项插入某值或删除首项 push pop 是在数组尾部操作

3、timer.apply(self,arr);        
timer是形参,引用了定时器setTimeout,apply是改变this的指向并可以数组的形式传入参数作为
定时器执行的形参,定时器this的指向为self
self就是this就是window,等价于 timer(arr[0],arr[1]);

4、'use strict'
严格模式,在严格模式下 arguments.callee(正在执行的这个函数的引用)无效 5、restart()
递归 6、promise的状态变成Resolved,就会触发then绑定的回调函数,
所以每次then都是return一个promsise对象,因为在promise对象中状态变成了Resolved 下面是实现的demo
    
注意:立即执行函数的script要写入body里面,否则会显示dom操作获得元素为null,我刚踩到这个坑了...

总结

  这个问题的解决让我重新认识了promise,又重新认识了arguments,又重新认识了JS的强大。

  红绿灯大战后,promise开发的最佳实践是怎样的?

  前端要给力之:红绿灯大战中的火星生命-Promise

  ECMAScript 6 入门(阮一峰)

 

面试 | 商汤科技面试经历之Promise红绿灯的实现的更多相关文章

  1. 商汤科技汤晓鸥:其实不存在AI行业,唯一存在的是“AI+“行业

    https://mp.weixin.qq.com/s/bU-TFh8lBAF5L0JrWEGgUQ 9 月 17 日,2018 世界人工智能大会在上海召开,在上午主论坛大会上,商汤科技联合创始人汤晓鸥 ...

  2. 计蒜客 第四场 C 商汤科技的行人检测(中等)平面几何好题

    商汤科技近日推出的 SenseVideo 能够对视频监控中的对象进行识别与分析,包括行人检测等.在行人检测问题中,最重要的就是对行人移动的检测.由于往往是在视频监控数据中检测行人,我们将图像上的行人抽 ...

  3. 2019 计蒜之道 初赛 第一场 商汤AI园区的n个路口(中等) (树形dp)

    北京市商汤科技开发有限公司建立了新的 AI 人工智能产业园,这个产业园区里有 nn 个路口,由 n - 1n−1 条道路连通.第 ii 条道路连接路口 u_iui​ 和 v_ivi​. 每个路口都布有 ...

  4. 旷视向左、商汤向右,AI一哥之名将落谁家

    编辑 | 于斌 出品 | 于见(mpyujian) AI风口历经多年洗礼之后,真正意义上的AI第一股终于要来了. 相比于聚焦在语音识别技术上的科大讯飞.立足互联网产业的百度.发力人形机器人领域的优必选 ...

  5. 回客科技 面试的 实现ioc 容器用到的技术,简述BeanFactory的实现原理,大搜车面试的 spring 怎么实现的依赖注入(DI)

    前言:这几天的面试,感觉自己对spring 的整个掌握还是很薄弱.所以需要继续加强. 这里说明一下spring的这几个面试题,但是实际的感觉还是不对的,这种问题我认为需要真正读了spring的源码后说 ...

  6. 面试北京XX科技总结

    1             面试时间与地点 面试时间:2019年1月17号,面试地点:北京. 2             公司概况 开发的产品是集团内部使用,开发的语言ts脚本语言.目前开发团队15人 ...

  7. [Interview]读懂面试问题,在面试官面前变被动为主动

    面试是供需双方心理的较量,作为求职者来说,了解对方问题的内涵,做到“明明白白他的心”,就能变被动为主动.因此,读懂面试问题,掌握面试考官的提问的目的,有准备.有针对性地回答,对提高应聘的成功率是有很大 ...

  8. 如何准备Java面试?如何把面试官的提问引导到自己准备好的范围内?

    Java能力和面试能力,这是两个方面的技能,可以这样说,如果不准备,一些大神或许也能通过面试,但能力和工资有可能被低估.再仔细分析下原因,面试中问的问题,虽然在职位介绍里已经给出了范围,但针对每个点, ...

  9. Android开发面试经——6.常见面试官提问Android题②(更新中...)

    版权声明:本文为寻梦-finddreams原创文章,请关注:http://blog.csdn.net/finddreams 关注finddreams博客:http://blog.csdn.net/fi ...

随机推荐

  1. nopCommerce 3.9 大波浪系列 之 引擎 NopEngine

    本章涉及到的内容如下 1.EngineContext初始化IEngine实例 2.Autofac依赖注入初始化 3.AutoMapper框架初始化 4.启动任务初始化 一.EngineContext初 ...

  2. Storm笔记——技术点汇总

    目录 概况 手工搭建集群 引言 安装Python 配置文件 启动与测试 应用部署 参数配置 Storm命令 原理 Storm架构 Storm组件 Stream Grouping 守护进程容错性(Dae ...

  3. Sqoop Java API 导入应用案例

    环境信息: Linux+JDK1.7 Sqoop 1.4.6-cdh5.5.2 hadoop-core 2.6.0-mr1-cdh5.5.2 hadoop-common 2.6.0-cdh5.5.2 ...

  4. MQ通道搭建以及连通性检查

    场景:项目开发中使用的mq中间件一直不太熟悉,遇到问题就需要问人,公司的同事也不怎么爱搭理,弄的好受伤!不熟悉的时候只是感觉好难,逼的没办法,好好研究下,发现里面的过程也没想象中的难, 经过一番研究, ...

  5. Nlpir Parser智能语义平台全文搜索

    全文索引用于处理大文本集合,利用它人们可以在海量文本中快速获取需要的信息.全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统.一般来说,全文检索需要具备建立索引和提供查询的基本功能 ...

  6. python的__init__几种方法总结

    参考 __init__() 这个方法一般用于初始化一个类 但是 当实例化一个类的时候, __init__并不是第一个被调用的, 第一个被调用的是__new__ #!/usr/bin/env pytho ...

  7. (转载)ubuntu创建新用户并增加管…

    问题导读: 1.adduser与useradd有什么区别? 2.那种方式会自动创建组.用户组等信息? 3.如何新建用户具有管理员权限? $是普通管员,#是系统管理员,在Ubuntu下,root用户默认 ...

  8. CODE大全告诉你java是否开始没落了

    CODE大全告诉你java是否开始没落了! 22 岁,对于一个技术人来说可谓正当壮年.但对于一门编程语言来说,情况可能又有不同.各类编程语言横空出世,纷战不休,然而 TIOBE 的语言排行榜上,Jav ...

  9. HPU--1221 Fibonacci数列

    题目描述 Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1. 当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少. 输入 输入包含一个整数n. ...

  10. NYOJ 69 数的长度(数学)

    数的长度 时间限制:3000 ms  |  内存限制:65535 KB 难度:1   描述 N!阶乘是一个非常大的数,大家都知道计算公式是N!=N*(N-1)······*2*1.现在你的任务是计算出 ...