摘要:經(jīng)過上網(wǎng)友的點(diǎn)撥,這個問題涉及到中的兩個問題,作用域鏈和事件執(zhí)行機(jī)制。常見的異步任務(wù)有定時器和事件回調(diào)等。異步任務(wù)三張圖片的事件依次觸發(fā),回調(diào)函數(shù)進(jìn)入任務(wù)隊(duì)列,等主線程的循環(huán)執(zhí)行完畢之后,依次執(zhí)行這三個任務(wù)。
之前工作中碰到一個需求,需要根據(jù)從后臺獲取到的圖片路徑獲得這些圖片的 base64 文件。實(shí)現(xiàn)過程中遇到一個問題,代碼如下:
var src=["http://www.w3school.com.cn/i/site_photoref.jpg", "http://www.w3school.com.cn/i/site_photoexa.jpg", "http://www.w3school.com.cn/i/site_photoqe.jpg"] for(var i=0;i<3;i++){ var img=new Image(); img.src=src[i]; img.onload=function(){ console.log(img) //最終打印出來都是最后一個圖片 // // // } }
這個問題其實(shí)跟之前經(jīng)常碰到的一個面試題本質(zhì)上是一致的。我們可以在上面函數(shù)中打印索引值,會發(fā)現(xiàn)打印出來的值都是3。經(jīng)過 segmentfault 上網(wǎng)友的點(diǎn)撥,這個問題涉及到 js 中的兩個問題,作用域鏈和事件執(zhí)行機(jī)制。
作用域鏈在 js 中,每個函數(shù)都有自己的執(zhí)行環(huán)境,每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象。當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。每個作用域鏈的起點(diǎn)都是當(dāng)前執(zhí)行代碼所在的執(zhí)行環(huán)境的變量對象,作用域鏈的下一個變量對象來自包含環(huán)境,一直延續(xù)到全局執(zhí)行環(huán)境(瀏覽器中是指 window 對象)。
如果執(zhí)行環(huán)境是函數(shù),那么它的變量對象就包括活動對象,活動對象在一開始只包括 arguments 對象(函數(shù)的參數(shù)對象)。
當(dāng)執(zhí)行環(huán)境中要用到某個變量或者函數(shù)時,會從自己作用域鏈的起點(diǎn)也就是自己的變量對象中開始搜索相應(yīng)的變量名或者函數(shù)名,如果搜索不到就接著在作用域鏈的上一級搜索,一直到找到相關(guān)變量名或者到作用域鏈的末尾為止。
js 事件執(zhí)行機(jī)制js 是一種單線程語言,在主線程中同一時間只能執(zhí)行一個任務(wù)。
瀏覽器內(nèi)核線程瀏覽器內(nèi)核是多線程的,通常包含以下線程:
GUI 渲染線程:
負(fù)責(zé)渲染網(wǎng)頁,當(dāng)頁面需要重繪時,該線程就會執(zhí)行。
JavaScript 引擎線程:
也就是 JS 內(nèi)核,負(fù)責(zé)解析和運(yùn)行 JS 代碼。
定時器觸發(fā)器線程:
通過這個線程計(jì)時來確定什么時候觸發(fā)定時器。
事件觸發(fā)線程:
監(jiān)控某個事件是否觸發(fā),事件觸發(fā)之后會被添加到任務(wù)隊(duì)列中。
異步 HTTP 請求線程:
監(jiān)控 AJAX 的狀態(tài)變更時,就會把相應(yīng)的任務(wù)添加到任務(wù)隊(duì)列中。
js 中每個任務(wù)的操作可以簡化為發(fā)起調(diào)用和獲得結(jié)果兩步,根據(jù)這兩步可以把js 中的任務(wù)可以分為同步任務(wù)和異步任務(wù)。所有任務(wù)的執(zhí)行都在主線程進(jìn)行。
同步任務(wù):發(fā)起調(diào)用之后,立即就會執(zhí)行來獲取結(jié)果的任務(wù)。調(diào)用之后會一直等待直到返回結(jié)果,在這期間主線程不能進(jìn)行其他操作。
異步任務(wù):發(fā)起調(diào)用之后,并不會立即執(zhí)行相關(guān)函數(shù),而是需要額外的操作滿足相關(guān)條件之后進(jìn)行觸發(fā)。相關(guān)任務(wù)被觸發(fā)之后會進(jìn)入任務(wù)隊(duì)列等待主線程任務(wù)執(zhí)行完成后按順序進(jìn)入主線程,調(diào)用和執(zhí)行之間的時間可以介入其他異步任務(wù)。常見的異步任務(wù)有定時器、ajax和事件回調(diào)等。
js 中事件執(zhí)行基本按照下面這三步進(jìn)行循環(huán)。
主線程先按照代碼順序執(zhí)行同步任務(wù)
在異步任務(wù)被注冊之后,瀏覽器的其他線程(事件觸發(fā)線程、定時器觸發(fā)線程、異步 HTTP 請求線程)監(jiān)控異步任務(wù)的觸發(fā)條件,按照觸發(fā)順序把這些異步任務(wù)放在任務(wù)隊(duì)列中
主線程上同步任務(wù)執(zhí)行完之后,會依次執(zhí)行任務(wù)隊(duì)列中的任務(wù)
回到開頭在最上面的例子中,for 循環(huán)是同步任務(wù),會立即執(zhí)行,圖片的 onload 事件是異步任務(wù),需要等另行觸發(fā)。這個例子中代碼的執(zhí)行順序是這樣:
同步任務(wù):循環(huán)創(chuàng)建三張圖片,每個圖片賦予各自的 src 值,并且都注冊了一個 onload 事件。
異步任務(wù):三張圖片的 onload 事件依次觸發(fā),回調(diào)函數(shù)進(jìn)入任務(wù)隊(duì)列,等主線程的 for 循環(huán)執(zhí)行完畢之后,依次執(zhí)行這三個任務(wù)。
當(dāng)開始執(zhí)行異步任務(wù)時,每個函數(shù)都需要用到 img 這個變量,就開始在自己的作用域鏈上開始尋找 img,自身變量對象中不存在,接著在包含環(huán)境中找到,由于 for 循環(huán)并不會創(chuàng)造一個新的執(zhí)行環(huán)境,所以這個例子中包含環(huán)境其實(shí)就是全局執(zhí)行環(huán)境。而在 for 循環(huán)完之后,img 變量的值已經(jīng)經(jīng)過兩次覆蓋變成了最后一個索引對應(yīng)的圖片。所以每個圖片的 onload 函數(shù)都會打印出同一個 img 。打印 i 值出現(xiàn)的結(jié)果也是一樣。
弄清楚出現(xiàn)這個問題的原因,解決這個問題可以用下面的辦法:
方法1:創(chuàng)建多帶帶的執(zhí)行環(huán)境for(var i=0;i<3;i++){ (function(index){ var img=new Image(); img.src=src[i]; img.onload=function(){ console.log(index) console.log(img) } })(i) }
這個方法實(shí)現(xiàn)的原理是:for 循環(huán)中立即執(zhí)行函數(shù)每次都會創(chuàng)建一個新的執(zhí)行環(huán)境,三張圖片的 onload 事件函數(shù)的作用域鏈的包含環(huán)境分別是這三個立即執(zhí)行函數(shù),這三個立即執(zhí)行函數(shù)里面保存的是不同的 img 變量和不同的參數(shù) index,當(dāng)三個 onload 回調(diào)函數(shù)執(zhí)行時,分別在自己的作用域鏈上尋找各自對應(yīng)的 img 變量。利用同樣的原理,也可以寫成這樣:
for(var i=0;i<3;i++){ var img=new Image(); img.src=src[i]; img.onload=function(index,img){ return function(){ console.log(index) console.log(img) } }(i,img) }
也可以不通過傳參,而是在立即執(zhí)行函數(shù)內(nèi)部創(chuàng)建一個變量來接收每次循環(huán)中的 i 值,原理都是一樣的。
方法2:訪問事件觸發(fā)節(jié)點(diǎn)for(var i=0;i<3;i++){ var img=new Image(); img.src=src[i]; img.onload=function(){ console.log(this) } }
這個方法的原理是:函數(shù)內(nèi)部在執(zhí)行過程中會有一個默認(rèn)的 this 變量會把函數(shù)的調(diào)用對象保存起來,通過函數(shù)內(nèi)部的 this 就可以訪問調(diào)用函數(shù)的對象。或者可以通過 event 事件對象的 currentTarget 屬性訪問到事件觸發(fā)節(jié)點(diǎn),原理是一樣的。
方法3:ES6 的新語法 letfor(let i=0;i<3;i++){ let img=new Image(); img.src=src[i]; img.onload=function(){ console.log(img) console.log(i) } }
在 ES6 中規(guī)定了一個新的變量聲明命令 let,let 會創(chuàng)建一個塊級作用域,用 let 聲明的變量只在 let 所在的代碼塊中有效。這個例子中,三次循環(huán)會創(chuàng)建三個塊級作用域,每個塊級作用域中有各自的變量 i 和 img,互相獨(dú)立,每個 onload 回調(diào)函數(shù)執(zhí)行時都會獲取各自代碼塊中的 i 和 img ,最終能實(shí)現(xiàn)我們想要的結(jié)果。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/82587.html
摘要:瀏覽器的主要組成包括有調(diào)用堆棧,事件循環(huán),任務(wù)隊(duì)列和。好了,現(xiàn)在有了前面這些知識,我們可以看一下這道題的講解過程實(shí)現(xiàn)步驟調(diào)用會將函數(shù)放入調(diào)用堆棧。由于調(diào)用堆棧是空的,事件循環(huán)將選擇回調(diào)并將其推入調(diào)用堆棧進(jìn)行處理。進(jìn)程再次重復(fù),堆棧不會溢出。 JavaScript是前端開發(fā)中非常重要的一門語言,瀏覽器是他主要運(yùn)行的地方。JavaScript是一個非常有意思的語言,但是他有很多一些概念,大...
摘要:今天同學(xué)去面試,做了兩道面試題全部做錯了,發(fā)過來給道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 排序算法 -- JavaScript 標(biāo)準(zhǔn)參考教程(alpha) - 前端 - 掘金來自《JavaScript 標(biāo)準(zhǔn)參考教程(alpha)》,by 阮一峰 目錄 冒泡排序 簡介 算法實(shí)現(xiàn) 選擇排序 簡介 算法實(shí)現(xiàn) ... 圖例詳解那道 setTimeout 與循環(huán)閉包的經(jīng)典面...
摘要:閉包正確的說應(yīng)該是指一個閉包域每當(dāng)聲明了一個函數(shù)它就產(chǎn)生了一個閉包域可以解釋為每個函數(shù)都有自己的函數(shù)棧每個閉包域?qū)ο蠖加幸粋€不是屬性內(nèi)默認(rèn)有個名為的全局引用有了這個引用就可以直接調(diào)用的屬性或方法凡是在閉包域內(nèi)聲明的變量或方法外部無法直接訪問 閉包 正確的說,應(yīng)該是指一個閉包域,每當(dāng)聲明了一個函數(shù),它就產(chǎn)生了一個閉包域(可以解釋為每個函數(shù)都有自己的函數(shù)棧),每個閉包域(Function...
閱讀 3469·2023-04-25 21:43
閱讀 3098·2019-08-29 17:04
閱讀 797·2019-08-29 16:32
閱讀 1533·2019-08-29 15:16
閱讀 2144·2019-08-29 14:09
閱讀 2732·2019-08-29 13:07
閱讀 1623·2019-08-26 13:32
閱讀 1320·2019-08-26 12:00