No2.瀏覽器中的JavaScript執行機制

前段時間在《極客時間》上學了一個專欄,通篇略過,乾貨很多,但理解至關不夠透徹,因而計劃用幾周的時間,對本專欄內容用做者的總結以及本身的相對逐字理解,來個通篇的文字記錄學習,書讀百遍,其義自現。
本篇是這個專欄的第二章:《瀏覽器中的JavaScript執行機制》。本章分爲五節。

07|變量提高:JavaScript代碼是按順序執行的嗎?


本節主要講解執行上下文相關的內容。web

經過一些代碼的執行順序與經驗咱們知道:瀏覽器

  • 在執行過程當中,若使用了未聲明的變量,那麼 JavaScript 執行會報錯。
  • 在一個變量定義以前使用它,不會出錯,可是該變量的值會爲 undefined,而不是定義時的值。
  • 在一個函數定義以前使用它,不會出錯,且函數能正確執行。
變量提高

所謂的變量提高,是指在 JavaScript 代碼執行過程當中,JavaScript 引擎把變量的聲明部分和函數的聲明部分提高到代碼開頭的「行爲」。變量被提高後,會給變量設置默認值,這個默認值就是咱們熟悉的 undefined.
之因此會發生變量提高,是由於一段JavaScript代碼在執行以前,須要被JavaScript引擎編譯,編譯完成以後,纔會進入執行階段。也就是說在編譯階段,變量和函數的聲明提高到了開頭。bash

08 |調用棧:爲何JavaScript代碼會出現棧溢出?


通常有三種狀況,當一段代碼執行的時候JS引擎對其進行編譯並建立執行上下文:微信

  1. 當 JavaScript 執行全局代碼的時候,會編譯全局代碼並建立全局執行上下文,並且在整個頁面的生存週期內,全局執行上下文只有一份.
  2. 當調用一個函數的時候,函數體內的代碼會被編譯,並建立函數執行上下文,通常狀況下,函數執行結束以後,建立的函數執行上下文會被銷燬。
  3. 當使用 eval 函數的時候,eval 的代碼也會被編譯,並建立執行上下文。
小結
  • 每調用一個函數,JavaScript 引擎會爲其建立執行上下文,並把該執行上下文壓入調用棧,而後 JavaScript 引擎開始執行函數代碼。
  • 若是在一個函數 A 中調用了另一個函數 B,那麼 JavaScript 引擎會爲 B 函數建立執行上下文,並將 B 函數的執行上下文壓入棧頂。
  • 當前函數執行完畢後,JavaScript 引擎會將該函數的執行上下文彈出棧。
  • 當分配的調用棧空間被佔滿時,會引起「堆棧溢出」問題。

09 | 塊級做用域:var缺陷以及爲何要引入let和const


做用域

做用域是指在程序中定義變量的區域,該位置決定了變量的生命週期。通俗地理解,做用域就是變量與函數的可訪問範圍,即做用域控制着變量和函數的可見性和生命週期。
ES6出現以前,JS的做用域只有兩種:全局做用域函數做用域。 ES6出現,引入了塊級做用域閉包

在同一段代碼中,ES6 是如何作到既要支持變量提高的特性,又要支持塊級做用域的呢?

當一段代碼裏面既有var聲明的變量也有let聲明的變量的時候:app

  • 函數內部經過var聲明的變量,在編譯階段全都被存放到變量環境裏面.
  • 經過let聲明的變量,在編譯階段會被存放到詞法環境中。
  • 在函數做用域內部,經過let聲明的變量並無被存放到詞法環境中。
    也就是說:經過理解詞法環境的結構和工做機制,塊級做用域是經過詞法環境的棧結構來實現的,而變量提高是經過變量環境來實現的,經過二者的結合,JavaScript引擎也就同時支持了變量 提高和塊級做用域了。

10 | 做用域和閉包:代碼中出現相同的變量,JavaScript引擎是如何選擇的


做用域鏈

理解做用域鏈是理解閉包的基礎,而閉包在JavaScript中無處不在,同時做用域和做用域鏈仍是做用語言的基礎,因此咱們先來學習一下做用域鏈。 理解了調用棧、執行上下文、詞法環境、變量環境等概念,那麼你理解起來做用域鏈也會很容易,看下面一段代碼:函數

function bar() {
   console.log(myName)
}
function foo() {
   var myName = "局部變量"
   bar()
}
var myName = "全局變量"
foo()
複製代碼

經過上面的代碼,咱們知道最終打印出來的結果是:」全局變量「。
這是由於,當一段代碼使用了一個變量後,JavaScript引擎會首先在「當前的執行上下文」中去查找該變量。若沒有找到,因爲每一個執行上下文都包含一個外部引用指向外部執行上下文,因此bar函數中的變量會去全局上下文中區域查找。咱們把這個查找的鏈條就稱爲做用域鏈。學習

詞法做用域

foo 函數調用的 bar 函數,那爲何 bar 函數的外部引用是全局執行上下文,而不是 foo 函數的執行上下文?瞭解這個問題咱們繼續來學習詞法做用域:
詞法做用域就是指做用域是由代碼中函數聲明的位置來決定的,因此詞法做用域是靜態的做用域,經過它就可以預測代碼在執行過程當中如何查找標識符。
而後,根據詞法做用域,foo 和 bar 的上級做用域都是全局做用域,因此若是 foo 或者 bar 函數使用了一個它們沒有定義的變量,那麼它們會到全局做用域去查找。也就是說,詞法做用域是代碼階段就決定好的,和函數是怎麼調用的沒有關係。this

閉包

在 JavaScript 中,根據詞法做用域的規則,內部函數老是能夠訪問其外部函數中聲明的變量,當經過調用一個外部函數返回一個內部函數後,即便該外部函數已經執行結束了,可是內部函數引用外部函數的變量依然保存在內存中,咱們就把這些變量的集合稱爲閉包。好比外部函數是 foo,那麼這些變量的集合就稱爲 foo 函數的閉包。 在使用閉包的時候,要儘可能注意一個原則:若是該閉包會一直使用,那麼它能夠做爲全局變量而存在;但若是使用頻率不高,並且佔用內存又比較大的話,那就儘可能讓它成爲一個局部變量。spa

11 | this:從JavaScript執行上下文的視角講清楚this


首先咱們要知道,在對象內部的方法中使用對象內部的屬性是一個很是廣泛的需求,可是JavaScript做用域機制並不支持這一點,基於這個需求,JavaScript搞出了一套this機制。

在前幾節中,咱們提到執行上下文中包含了:變量環境詞法環境外部環境、還有一個沒有說起的this,this是和執行上下文綁定的,每一個執行上下文都有一個this。
在08節咱們總結了執行上下文主要分三種:全局執行上下文、函數執行上下文和eval執行上下文。
對應的this也只有這三種:全局執行上下文中的this、函數執行上下中的this和eval中的this(不作討論)。

  • 全局執行上下文中的this:全局執行上下文中的this指向window對象。
  • 函數執行上下文中的this:
    1. 默認狀況下調用一個函數,其執行上下文中的 this 也是指向 window 對象的.
    2. 經過函數的call方法設置其this指向其餘對象(還可使用bind和apply方法來設置函數執行上下文中的this)。
    3. 經過對象調用方法設置。(使用對象來調用其內部的一個方法,該方法的 this 是指向對象自己的。在全局環境中調用一個函數,函數內部的this指向的是全局變量window)。
    4. 經過構造函數中設置。
this的設計缺陷以及應對方案
  1. 嵌套函數的this不會從外層函數中繼承。==> 1⃣️、將this保存一個self變量,利用變量做用域機制傳遞給嵌套函數。2⃣️、將喬套函數改成箭頭函數。
  2. 普通函數中的this默認指向全局對象window。==>能夠經過設置JavaScript的「嚴格模式」來解決。
世界有多大,取決於你認識和見過多少人和事。

若有疑問請添加個人微信號:18231133236。歡迎交流! 更多內容,請訪問的個人我的博客:www.liugezhou.online. 您也能夠關注個人我的公衆號:【Dangerous Wakaka】

image
相關文章
相關標籤/搜索