zoukankan      html  css  js  c++  java
  • 面试 | 商汤科技面试经历之Promise红绿灯的实现

    说在前面

      说实话,刚开始在听到这个面试题的时候,我是诧异的,红绿灯?这不是单片机、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 入门(阮一峰)

     

     
  • 相关阅读:
    form表单中有bootstrap-switch时怎么提交表单
    Django:drf过滤、搜索、排序功能
    pycharm:使用豆瓣源安装第三方库
    pycharm安装包时:Non-zero exit code (1)
    js: 前端通过ajax方式获取后台数据填充下拉列表
    Django:drf框架中序列化组件—字段验证
    ROS Reality: A Virtual Reality Framework Using Consumer-Grade Hardware for ROS-Enabled Robots David
    Virtual and Augmented Reality for Robotics in Unity environment using Mimic plugin
    Human-machine collaboration in virtual reality for adaptive production engineering
    An open modular framework for efficient and interactive simulation and analysis of realistic human motions for professional applications
  • 原文地址:https://www.cnblogs.com/dirkhe/p/7456251.html
Copyright © 2011-2022 走看看