說實話,剛開始在聽到這個面試題的時候,我是詫異的,紅綠燈?這不是單片機、FPGA、F2833五、PLC的實驗嗎?!css
並且還要用Promise去寫,當時我確實沒思路,只好硬着頭皮去寫,下來再review的時候,才真正懂了Promise紅綠燈的實現原理html
下來我就由淺至深的分析Promise紅綠燈的實現原理前端
下面我就不講promise的原理和特色了,想具體看了解的能夠看阮一峯老師的教程es6
主要說下紅綠燈用到promise最核心的一點就是 「promise實例的狀態變爲Resolved,就會觸發then方法綁定的回調函數」面試
我是在作這個demo途中才完全理解了這句話的真正含義。數組
用文字綠燈、黃燈、紅燈來模擬表示紅綠燈promise
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效果app

這種實現有一個問題,若是設定綠燈是5000ms,黃燈是2000ms,紅燈是3000ms,
則會出現先顯示黃燈,後顯示紅燈,顯示綠燈的同時也會同時顯示黃燈,
由於第二輪綠燈的5000ms包含了黃燈的2000ms
這就不符合紅綠燈的思想與邏輯
針對上一個問題,因此有了第二種解決方案dom
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() }) })();
一、var arr = [].slice.apply(arguments)
表示把arguments轉成數組
二、arr.unshift(resolve)
unshift或shift 在數組首項插入某值或刪除首項 push pop 是在數組尾部操做
三、timer.apply(self,arr);
timer是形參,引用了定時器setTimeout,apply是改變this的指向並能夠數組的形式傳入參數做爲
定時器執行的形參,定時器this的指向爲self
self就是this就是window,等價於 timer(arr[0],arr[1]);
四、'use strict'
嚴格模式,在嚴格模式下 arguments.callee(正在執行的這個函數的引用)無效
五、restart()
遞歸
六、promise的狀態變成Resolved,就會觸發then綁定的回調函數,
因此每次then都是return一個promsise對象,由於在promise對象中狀態變成了Resolved
下面是實現的demo
注意:當即執行函數的script要寫入body裏面,不然會顯示dom操做得到元素爲null,我剛踩到這個坑了...
這個問題的解決讓我從新認識了promise,又從新認識了arguments,又從新認識了JS的強大。