本節主要講解
執行上下文
相關的內容。web
經過一些代碼的執行順序與經驗咱們知道:瀏覽器
- 在執行過程當中,若使用了未聲明的變量,那麼 JavaScript 執行會報錯。
- 在一個變量定義以前使用它,不會出錯,可是該變量的值會爲 undefined,而不是定義時的值。
- 在一個函數定義以前使用它,不會出錯,且函數能正確執行。
所謂的變量提高,是指在 JavaScript 代碼執行過程當中,JavaScript 引擎把變量的聲明部分和函數的聲明部分提高到代碼開頭的「行爲」。變量被提高後,會給變量設置默認值,這個默認值就是咱們熟悉的 undefined.
之因此會發生變量提高,是由於一段JavaScript代碼在執行以前,須要被JavaScript引擎編譯,編譯完成以後,纔會進入執行階段。也就是說在編譯階段,變量和函數的聲明提高到了開頭。bash
通常有三種狀況,當一段代碼執行的時候JS引擎對其進行編譯並建立執行上下文:微信
- 當 JavaScript 執行全局代碼的時候,會編譯全局代碼並建立全局執行上下文,並且在整個頁面的生存週期內,全局執行上下文只有一份.
- 當調用一個函數的時候,函數體內的代碼會被編譯,並建立函數執行上下文,通常狀況下,函數執行結束以後,建立的函數執行上下文會被銷燬。
- 當使用 eval 函數的時候,eval 的代碼也會被編譯,並建立執行上下文。
- 每調用一個函數,JavaScript 引擎會爲其建立執行上下文,並把該執行上下文壓入調用棧,而後 JavaScript 引擎開始執行函數代碼。
- 若是在一個函數 A 中調用了另一個函數 B,那麼 JavaScript 引擎會爲 B 函數建立執行上下文,並將 B 函數的執行上下文壓入棧頂。
- 當前函數執行完畢後,JavaScript 引擎會將該函數的執行上下文彈出棧。
- 當分配的調用棧空間被佔滿時,會引起「堆棧溢出」問題。
做用域是指在程序中定義變量的區域,該位置決定了變量的生命週期。通俗地理解,做用域就是變量與函數的可訪問範圍,即做用域控制着變量和函數的可見性和生命週期。
ES6出現以前,JS的做用域只有兩種:全局做用域
和函數做用域
。 ES6出現,引入了塊級做用域
。閉包
當一段代碼裏面既有var聲明的變量也有let聲明的變量的時候:app
- 函數內部經過var聲明的變量,在編譯階段全都被存放到
變量環境
裏面.- 經過let聲明的變量,在編譯階段會被存放到
詞法環境
中。- 在函數做用域內部,經過let聲明的變量並無被存放到詞法環境中。
也就是說:經過理解詞法環境的結構和工做機制,塊級做用域是經過詞法環境的棧結構來實現的,而變量提高是經過變量環境來實現的,經過二者的結合,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
首先咱們要知道,在對象內部的方法中使用對象內部的屬性是一個很是廣泛的需求,可是JavaScript做用域機制並不支持這一點,基於這個需求,JavaScript搞出了一套this機制。
在前幾節中,咱們提到執行上下文中包含了:
變量環境
、詞法環境
、外部環境
、還有一個沒有說起的this
,this是和執行上下文綁定的,每一個執行上下文都有一個this。
在08節咱們總結了執行上下文主要分三種:全局執行上下文、函數執行上下文和eval執行上下文。
對應的this也只有這三種:全局執行上下文中的this、函數執行上下中的this和eval中的this(不作討論)。
- 全局執行上下文中的this:全局執行上下文中的this指向window對象。
- 函數執行上下文中的this:
- 默認狀況下調用一個函數,其執行上下文中的 this 也是指向 window 對象的.
- 經過函數的call方法設置其this指向其餘對象(還可使用bind和apply方法來設置函數執行上下文中的this)。
- 經過對象調用方法設置。(使用對象來調用其內部的一個方法,該方法的 this 是指向對象自己的。在全局環境中調用一個函數,函數內部的this指向的是全局變量window)。
- 經過構造函數中設置。
- 嵌套函數的this不會從外層函數中繼承。==> 1⃣️、將this保存一個self變量,利用變量做用域機制傳遞給嵌套函數。2⃣️、將喬套函數改成箭頭函數。
- 普通函數中的this默認指向全局對象window。==>能夠經過設置JavaScript的「嚴格模式」來解決。
若有疑問請添加個人微信號:18231133236。歡迎交流! 更多內容,請訪問的個人我的博客:www.liugezhou.online. 您也能夠關注個人我的公衆號:【Dangerous Wakaka】