Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 日本a中文字幕,欧美精品成人一区二区视频一,中国一级毛片录像

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          JavaScript運行機(jī)制

          JavaScript運行機(jī)制

          、JavaScript是單線程

          為什么是單線:因為JavaScript主要用于DOM操作和用戶交互,如果說是多線程,一個線程要在某個DOM節(jié)點上添加元素,另一個線程是要刪除這個節(jié)點。這樣就存在一個問題,到底是以哪個線程為主呢?為了避免復(fù)雜性,設(shè)計者一開始就把它設(shè)定為單線程。

          ?二、任務(wù)隊列

          因為是單線程,所以JavaScript執(zhí)行任務(wù)就需要一個一個的來,這樣就造成大量任務(wù)排隊執(zhí)行,效率很低,為了解決這個問題就提到了同步任務(wù)+異步任務(wù)。

          同步任務(wù):就是主線程執(zhí)行的任務(wù)。

          異步任務(wù):異步任務(wù)是不進(jìn)入主線程,而是進(jìn)入“任務(wù)隊列”中。

          JavaScript運行機(jī)制:就是主線程執(zhí)行的是同步任務(wù),主線程執(zhí)行完畢后執(zhí)行任務(wù)隊列中的事件(任務(wù)隊列中放置的是異步任務(wù))

          主線程從"任務(wù)隊列"中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機(jī)制又稱為Event Loop(事件循環(huán))

          三、異步任務(wù)有哪些

          定時器、網(wǎng)絡(luò)請求(http)、Promise、I/o、UI渲染

          所周知,JavaScript 是一門單線程語言,雖然在 html5 中提出了 Web-Worker ,但這并未改變 JavaScript 是單線程這一核心。可看HTML規(guī)范中的這段話:

          To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.

          為了協(xié)調(diào)事件、用戶交互、腳本、UI 渲染和網(wǎng)絡(luò)處理等行為,用戶引擎必須使用 event loops。Event Loop 包含兩類:一類是基于 Browsing Context ,一種是基于 Worker ,二者是獨立運行的。 下面本文用一個例子,著重講解下基于 Browsing Context 的事件循環(huán)機(jī)制。

          來看下面這段 JavaScript 代碼:

           
          setTimeout(function() {
           console.log('setTimeout');
          }, 0);//前端全棧交流學(xué)習(xí)圈:866109386
          //幫助1-3年前端人員,突破技術(shù),提升思維 
          Promise.resolve().then(function() {
           console.log('promise1');
          }).then(function() {
           console.log('promise2');
          });
           
          console.log('script end');
          

          先猜測一下這段代碼的輸出順序是什么,再去瀏覽器控制臺輸入一下,看看實際輸出的順序和你猜測出的順序是否一致,如果一致,那就說明,你對 JavaScript 的事件循環(huán)機(jī)制還是有一定了解的,繼續(xù)往下看可以鞏固下你的知識;而如果實際輸出的順序和你的猜測不一致,那么本文下面的部分會為你答疑解惑。

          任務(wù)隊列

          所有的任務(wù)可以分為同步任務(wù)和異步任務(wù),同步任務(wù),顧名思義,就是立即執(zhí)行的任務(wù),同步任務(wù)一般會直接進(jìn)入到主線程中執(zhí)行;而異步任務(wù),就是異步執(zhí)行的任務(wù),比如ajax網(wǎng)絡(luò)請求,setTimeout 定時函數(shù)等都屬于異步任務(wù),異步任務(wù)會通過任務(wù)隊列( Event Queue )的機(jī)制來進(jìn)行協(xié)調(diào)。具體的可以用下面的圖來大致說明一下:



          同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行環(huán)境,同步的進(jìn)入主線程,即主執(zhí)行棧,異步的進(jìn)入 Event Queue 。主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會去 Event Queue 讀取對應(yīng)的任務(wù),推入主線程執(zhí)行。 上述過程的不斷重復(fù)就是我們說的 Event Loop (事件循環(huán))。

          在事件循環(huán)中,每進(jìn)行一次循環(huán)操作稱為tick,通過閱讀規(guī)范可知,每一次 tick 的任務(wù)處理模型是比較復(fù)雜的,其關(guān)鍵的步驟可以總結(jié)如下:

          • 在此次 tick 中選擇最先進(jìn)入隊列的任務(wù)( oldest task ),如果有則執(zhí)行(一次)
          • 檢查是否存在 Microtasks ,如果存在則不停地執(zhí)行,直至清空Microtask Queue
          • 更新 render

          主線程重復(fù)執(zhí)行上述步驟

          可以用一張圖來說明下流程:



          這里相信有人會想問,什么是 microtasks ?規(guī)范中規(guī)定,task分為兩大類, 分別是 Macro Task (宏任務(wù))和 Micro Task(微任務(wù)), 并且每個宏任務(wù)結(jié)束后, 都要清空所有的微任務(wù),這里的 Macro Task也是我們常說的 task ,有些文章并沒有對其做區(qū)分,后面文章中所提及的task皆看做宏任務(wù)( macro task)。

          (macro)task 主要包含:script( 整體代碼)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 環(huán)境)

          microtask主要包含:Promise、MutaionObserver、process.nextTick(Node.js 環(huán)境)

          setTimeout/Promise 等API便是任務(wù)源,而進(jìn)入任務(wù)隊列的是由他們指定的具體執(zhí)行任務(wù)。來自不同任務(wù)源的任務(wù)會進(jìn)入到不同的任務(wù)隊列。其中 setTimeout 與 setInterval 是同源的。

          分析示例代碼

          千言萬語,不如就著例子講來的清楚。下面我們可以按照規(guī)范,一步步執(zhí)行解析下上面的例子,先貼一下例子代碼(免得你往上翻)。

          console.log('script start');
           
          setTimeout(function() {
           console.log('setTimeout');
          }, 0);//前端全棧交流學(xué)習(xí)圈:866109386
           //幫助1-3年前端人員,提升技術(shù),突破思維
          Promise.resolve().then(function() {
           console.log('promise1');
          }).then(function() {
           console.log('promise2');
          });
           
          console.log('script end');
          

          整體 script 作為第一個宏任務(wù)進(jìn)入主線程,遇到 console.log,輸出 script start

          遇到 setTimeout,其回調(diào)函數(shù)被分發(fā)到宏任務(wù) Event Queue 中

          遇到 Promise,其 then函數(shù)被分到到微任務(wù) Event Queue 中,記為 then1,之后又遇到了 then 函數(shù),將其分到微任務(wù) Event Queue 中,記為 then2

          遇到 console.log,輸出 script end

          至此,Event Queue 中存在三個任務(wù),如下表:

          • 執(zhí)行微任務(wù),首先執(zhí)行then1,輸出 promise1, 然后執(zhí)行 then2,輸出 promise2,這樣就清空了所有微任務(wù)
          • 執(zhí)行 setTimeout 任務(wù),輸出 setTimeout 至此,輸出的順序是:script start, script end, promise1, promise2, setTimeout
          • so,你猜對了嗎?

          看看你掌握了沒

          再來一個題目,來做個練習(xí):

          console.log('script start');
           
          setTimeout(function() {
           console.log('timeout1');
          }, 10);
           
          new Promise(resolve=> {
           console.log('promise1');
           resolve();
           setTimeout(()=> console.log('timeout2'), 10);
          }).then(function() {
           console.log('then1')
          })
           
          console.log('script end');
          

          這個題目就稍微有點復(fù)雜了,我們再分析下:

          首先,事件循環(huán)從宏任務(wù) (macrotask) 隊列開始,最初始,宏任務(wù)隊列中,只有一個 scrip t(整體代碼)任務(wù);當(dāng)遇到任務(wù)源 (task source) 時,則會先分發(fā)任務(wù)到對應(yīng)的任務(wù)隊列中去。所以,就和上面例子類似,首先遇到了console.log,輸出 script start; 接著往下走,遇到 setTimeout 任務(wù)源,將其分發(fā)到任務(wù)隊列中去,記為 timeout1; 接著遇到 promise,new promise 中的代碼立即執(zhí)行,輸出 promise1, 然后執(zhí)行 resolve ,遇到 setTimeout ,將其分發(fā)到任務(wù)隊列中去,記為 timemout2, 將其 then 分發(fā)到微任務(wù)隊列中去,記為 then1; 接著遇到 console.log 代碼,直接輸出 script end 接著檢查微任務(wù)隊列,發(fā)現(xiàn)有個 then1 微任務(wù),執(zhí)行,輸出then1 再檢查微任務(wù)隊列,發(fā)現(xiàn)已經(jīng)清空,則開始檢查宏任務(wù)隊列,執(zhí)行 timeout1,輸出 timeout1; 接著執(zhí)行 timeout2,輸出 timeout2 至此,所有的都隊列都已清空,執(zhí)行完畢。其輸出的順序依次是:script start, promise1, script end, then1, timeout1, timeout2

          用流程圖看更清晰:



          總結(jié)

          有個小 tip:從規(guī)范來看,microtask 優(yōu)先于 task 執(zhí)行,所以如果有需要優(yōu)先執(zhí)行的邏輯,放入microtask 隊列會比 task 更早的被執(zhí)行。

          最后的最后,記住,JavaScript 是一門單線程語言,異步操作都是放到事件循環(huán)隊列里面,等待主執(zhí)行棧來執(zhí)行的,并沒有專門的異步執(zhí)行線程。。

          對前端的技術(shù),架構(gòu)技術(shù)感興趣的同學(xué)關(guān)注我的頭條號,并在后臺私信發(fā)送關(guān)鍵字:“前端”即可獲取免費的架構(gòu)師學(xué)習(xí)資料

          知識體系已整理好,歡迎免費領(lǐng)取。還有面試視頻分享可以免費獲取。關(guān)注我,可以獲得沒有的架構(gòu)經(jīng)驗哦!!

          • 變量提升
          • 調(diào)用棧
          • 作用域鏈
          • 閉包
          • this

          變量提升

          實際上變量和函數(shù)聲明在代碼里的位置是不會變的,而且是在編譯階段被 JavaScript 引擎放入內(nèi)存中,一段 JavaScript 代碼在執(zhí)行之前需要被 JavaScript 引擎編譯,編譯完成之后,才會進(jìn)入執(zhí)行階段。大致流程為:JavaScript 代碼片段 ——> 編譯階段 ——> 執(zhí)行階段—>

          編譯階段,每段執(zhí)行代碼會分為兩部分,第一部分為變量提升部分的代碼,第二部分為執(zhí)行部分的代碼。經(jīng)過編譯后,生成執(zhí)行上下文(Execution context)和 可執(zhí)行代碼

          執(zhí)行上下文 是 JavaScript 執(zhí)行一段代碼時的運行環(huán)境,比如調(diào)用一個函數(shù),就會進(jìn)入函數(shù)的執(zhí)行上下文,從而確定該函數(shù)執(zhí)行期間用到的如 this、變量、對象以及函數(shù)等。

          執(zhí)行上下文由 變量環(huán)境(Variable Environment) 和 **詞法環(huán)境(Lexical Environment)**對象 組成,變量環(huán)境保存了代碼中變量提升的內(nèi)容,包括 var 定義和 function 定義的變量。而詞法環(huán)境保存 let 和 const 定義塊級作用域的變量。

          塊級作用域就是通過詞法環(huán)境的棧結(jié)構(gòu)來實現(xiàn)的,而變量提升是通過變量環(huán)境來實現(xiàn),通過這兩者的結(jié)合,JavaScript 引擎也就同時支持了變量提升和塊級作用域了

          變量查找過程:沿著詞法環(huán)境的棧頂向下查詢,如果在詞法環(huán)境中的某個塊中查找到了,就直接返回給 JavaScript 引擎,如果沒有查找到,那么繼續(xù)在變量環(huán)境中查找。

          變量聲明提升補(bǔ)充:

          • var的創(chuàng)建和初始化被提升,賦值不會被提升。
          • let的創(chuàng)建被提升,初始化和賦值不會被提升。
          • function的創(chuàng)建、初始化和賦值均會被提升。

          調(diào)用棧

          調(diào)用棧是用來管理函數(shù)調(diào)用關(guān)系的一種數(shù)據(jù)結(jié)構(gòu)。在函數(shù)調(diào)用的時候,JavaScript 引擎會創(chuàng)建函數(shù)執(zhí)行上下文,而全局代碼下又有一個全局執(zhí)行上下文,這些執(zhí)行上下文會使用一種叫的數(shù)據(jù)結(jié)果來管理。

          所以 JavaScript 的調(diào)用棧,其實就是 執(zhí)行上下文棧 。舉例代碼執(zhí)行,入棧如圖所示:

          var a=2
          function add(b,c){
            return b+c
          }
          function addAll(b,c){
          var d=10
          result=add(b,c)
          return  a+result+d
          }
          addAll(3,6)

          調(diào)用棧既然是一種數(shù)據(jù)結(jié)構(gòu),所以是存在大小的,超出了棧大小就會出現(xiàn)棧溢出報錯,比如斐波那契數(shù)列,執(zhí)行10000次,超過了最大棧調(diào)用大小(Maximum call stack size exceeded)。

          function Fibonacci2 (n , ac1=1 , ac2=1) {
            if( n <=1 ) {return ac2};
          
            return Fibonacci2 (n - 1, ac2, ac1 + ac2);
          }
          Fibonacci2(10000) // Maximum call stack size exceeded

          該函數(shù)是遞歸的,雖然只有一種函數(shù)調(diào)用,但是還是會一直創(chuàng)建執(zhí)行上下文壓入調(diào)用棧中,導(dǎo)致超過最大調(diào)用棧大小報錯,可以通過 Chrome 調(diào)式看到 Call Stack 的情況


          總結(jié):

          • 每調(diào)用一個函數(shù),JavaScript 引擎會為其創(chuàng)建執(zhí)行上下文,并把該執(zhí)行上下文壓入調(diào)用棧,然后 JavaScript 引擎開始執(zhí)行函數(shù)代碼。
          • 如果在一個函數(shù) A 中調(diào)用了另外一個函數(shù) B,那么 JavaScript 引擎會為 B 函數(shù)創(chuàng)建執(zhí)行上下文,并將 B 函數(shù)的執(zhí)行上下文壓入棧頂。
          • 當(dāng)前函數(shù)執(zhí)行完畢后,JavaScript 引擎會將該函數(shù)的執(zhí)行上下文彈出棧。
          • 當(dāng)分配的調(diào)用棧空間被占滿時,會引發(fā)“堆棧溢出”問題。

          所以,斐波那契數(shù)列函數(shù)優(yōu)化的手段就是使用循環(huán)來減少函數(shù)調(diào)用,從而減少函數(shù)執(zhí)行上下文的創(chuàng)建壓入棧的情況,就可以解決棧溢出的報錯了。(遞歸尾部優(yōu)化無法解決問題,Chrome瀏覽器還是棧溢出),使用蹦床函數(shù)來解決:

          function runStack (n) {
            if (n===0) return 100;
            return runStack.bind(null, n- 2); // 返回自身的一個版本
          }
          // 蹦床函數(shù),避免遞歸
          function trampoline(f) {
            while (f && f instanceof Function) {
              f=f();
            }
            return f;
          }
          trampoline(runStack(1000000))

          可以看到,調(diào)用棧中一直是保持3個執(zhí)行上下文而已,多余的都及時的pop掉了。

          作用域鏈

          每個執(zhí)行上下文的變量環(huán)境中,都包含了一個外部引用,用來指向外部的執(zhí)行上下文,我們把這個外部的引用稱為 outer

          當(dāng)一段代碼使用一個變量是,JavaScript 引擎首先會在“當(dāng)前的執(zhí)行上下文”中查找該變量,如果找不到就會繼續(xù)在 outer 所指向的執(zhí)行上下文中查找。我們把這個查找的鏈條就稱為作用域鏈

          詞法作用域

          詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來決定的,所以詞法作用域是靜態(tài)的作用域,通過它就能夠預(yù)測代碼在執(zhí)行過程中如何查找標(biāo)識符。詞法作用域是代碼階段決定好的,和函數(shù)是怎么調(diào)用的沒有關(guān)系。

          塊級作用域中的變量查找

          • 從當(dāng)前執(zhí)行上下文的詞法環(huán)境,自頂向下查找(棧中的內(nèi)存塊),然后再從當(dāng)前執(zhí)行向下文中的變量環(huán)境中查找;
          • 查找不到,則繼續(xù)在outer指向的執(zhí)行上下文繼續(xù)依次先從詞法環(huán)境,再到變量環(huán)境查找。

          閉包

          有詞法作用域的規(guī)則可以知道,內(nèi)部函數(shù)總是可以訪問他們的外部函數(shù)中的變量,當(dāng)外部函數(shù)執(zhí)行完畢后,pop stack了,遺留下了外部環(huán)境形成的閉包 Closure 環(huán)境,該環(huán)境內(nèi)存中還保存著那些可以訪問的變量,類似一個專屬背包,除了內(nèi)部函數(shù)訪問,氣氛方式無法訪問該專屬背包,我們就包這個背包稱為外部函數(shù)的閉包(那些內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中,我們把這些變量的集合稱為閉包)。

          閉包是怎么回收的

          如果引用閉包的函數(shù)是一個全局變量,那么閉包會一直存在知道頁面關(guān)閉;如果這個閉包以后不再使用的話,就會造成內(nèi)存泄漏。

          如果引用閉包的函數(shù)是一個局部變量,等函數(shù)銷毀后,下次 JavaScript 引擎執(zhí)行垃圾回收時,判斷閉包這塊內(nèi)容如果不再被使用了,那么 JavaScript 引擎的垃圾回收器就會回收這塊的內(nèi)存。

          使用閉包的原則:如果閉包會一直使用,那么它可以作為全局變量而存在;但如果使用頻率不高,而且占用內(nèi)存有比較大的話,那就盡量讓它成為一個局部變量。

          this

          let a={ name: 'this解釋' }
          function foo() {
            console.log(this.name)
          }
          foo.bind(a)() //=> 'this解釋''




          參考資源:《瀏覽器的工作原理與實踐》極客時間-李兵


          主站蜘蛛池模板: 天天视频一区二区三区| 一区在线免费观看| 波多野结衣一区在线| 国模私拍一区二区三区| 日本在线电影一区二区三区| 亚洲毛片αv无线播放一区| 国产在线一区二区杨幂| 无码囯产精品一区二区免费| 国产一区二区视频在线观看| 一区二区三区在线视频播放| 日韩色视频一区二区三区亚洲| 日本一区二区三区爆乳| 中文字幕在线一区二区在线| 国模私拍一区二区三区| 亚洲AV无一区二区三区久久| 国精产品一区一区三区| 精品国产一区二区三区久久| 日本精品高清一区二区| 一区二区乱子伦在线播放| 国产亚洲情侣一区二区无| 变态拳头交视频一区二区 | 久久亚洲一区二区| 九九久久99综合一区二区| 久久精品国产一区| 一区二区三区在线|欧| 精品视频一区二区三三区四区| 91精品一区二区三区久久久久| 亚洲狠狠久久综合一区77777| 免费无码一区二区三区| 无码人妻一区二区三区免费 | 丰满人妻一区二区三区视频| 精品无码成人片一区二区| 一区二区三区免费在线视频| 一区二区三区在线播放| 亚洲综合无码AV一区二区 | 精品一区二区三区电影| 日韩十八禁一区二区久久| 狠狠色婷婷久久一区二区| 亚洲视频一区调教| 国精产品一区一区三区有限公司| 日韩视频在线一区|