重新瞭解 javascript

最近學了很多東西,發現 js 的很多內容只是憑藉着我的 主觀意識,或者說 感覺。所以這需要系統地學習 以及整理一下

1、變量提升

首先要明確的一點是,在 es6 出現以前,js 只有五種作用域 ,

  1. 全局作用域
  2. 函數作用域
  3. try/catch 作用域
  4. eval 作用域
  5. with 作用域 (在 vue 模板語法的編譯之中,就廣泛地使用到了 這個)

後面三種 不談,就談談 前面兩個

console.log(name)  // function name
console.log(age)   // undefined

function name() {}
var name = 'dong'
var age = 24

可以看到,在這種情況下,就出現了 變量提升

  1. 在很多文章中,都提到了 變量提升是爲了解決 函數 調用的問題,但是 var 又是怎麼回事呢?
  2. 首先  js 在執行之前,會先進行一遍 編譯
  3. 編譯的過程中,並不會執行代碼
  4. 像上面的代碼所示,會 解析到 name ,那麼會 將 name 保存的環境變量中,但是 當前是一個函數,有 較高的優先級,會覆蓋掉 var 的undefined 定義,函數體 也被放到了 環境變量中
  5. 第二個 var name 就如 第四點 提到的,優先級 比函數低,所以內部的存儲 依舊是 function
  6. 到了 age ,則是將 age 放到環境變量中,並且默認賦值 爲 undefined
  7. 然後開始執行,輸入如代碼中所示

2、let 和 const

正是因爲 var 的變量提升以及 缺少 作用域 ,所以出現了 let 和const

  1. let 和 const 在 { } 中是存在作用域的,哪怕 那只是一個 空的 { }
  2. 在 上一點 提到的 編譯過程之中,其實 let 和 const 在 執行之前也被 編譯到了,那麼 這個是如何做到 暫時性死區 和 作用域的呢?
  3. 在 編譯到 let 和 const 之後,對應的值 就會被 放入 詞法環境(相當於單獨的環境變量)中,而不是 通常所說的 環境變量中,然後 引擎 在 執行的過程之中,會 特別 注意這一點,禁止 代碼在 定義之前 進行訪問
  4. 所以下面代碼的執行結果 就很清晰了
  5. 當 爲 var 的時候,由於 沒有作用域的影響,var 變量提升了,變成了 全局作用域,5 個 i 指向了 同一個
  6. 當 爲 let 的時候,由於 作用域的 影響,每一個 {} 都是一個單獨的作用域,所以 出現了 5 個 不同 的 i

3、作用域鏈

講作用域鏈,就肯定會談到 閉包,以及 閉包的原理了

  1. 閉包的定義,只和 詞法作用域 有關,而和在哪裏執行無關
  2. 也就是說,一個函數的閉包,只和 你在哪裏定義了 這個函數有關係
  3. 然後 引用 第二大點 的 let 和 const, 一個 作用域會先 從 詞法環境 裏面找,然後 再到 環境變量中找,一層一層得找下來,直到 匹配 或者 到全局環境 中發現未定義爲止
  4. 這裏又要講到函數 調用棧的 概念了,也就是 先進後出 的 關係
  5. 在上文中提到的編譯階段,遇到函數的時候,會快速地 對這個函數 做一次 詞法掃描,然後 將 函數中 用到的值保存到 堆空間中
  6. 所以 下面 輸出 的是 outer 也是一目瞭然的事情了

 

3、this

由於 閉包的缺陷(不能 手動 指定 作用域 到底是什麼 ),js 又引入了 this 的概念

  1. 嚴格模式下,函數調用的時候,內部指向的是 undefined ,而在非嚴格模式下,指向的是 window
  2. 嵌套函數中的 this 不會繼承 外層函數的  this,這裏就引出了 () => {}, 就像 下圖 Vue 中的 mounted 一樣,內部定義一個函數,結果 this 指向的是 undefined。。。這裏其實就和 第一點說的一樣,內部調用的時候,是 undefined
  3. 各種操作 this 的方法中,this 的指向 是有 權重的,總的來說,是 obj.fn < fn.bind(obj) / apply / call < new fn()
  4. 要注意的是,事件綁定(指向 當前 dom),以及 setTimeout 中的this 指向(指向 window)

4、那麼 js 是怎麼被解析的呢?

這裏面就涉及到了 v8引擎的底層實現,我肯定是不知道的,只講講表面

  1. 正如上文所講的,V8引擎 在執行之前 會編譯一遍代碼,
  2. 編譯代碼的時候,會將 你的代碼 分成 一個又一個的 token,也就是 最小的不可分割的部分,例如 var 、 = 、 name 等等
  3. 將 token 轉化爲 AST 抽象語法樹, 閉包、變量提升 就是在這個階段完成的(大名鼎鼎的 babel、ESlint 就是 基於 AST 語法樹  來進行 編譯的)
  4. 如果 成功便成 AST ,那麼自然相安無事,如果 變不成,那麼就是 你寫的代碼有問題,出現了語法錯誤,這個時候就會拋出一個錯誤
  5. 但是 AST 依舊處於高層,所以 V8 會將 語法樹 編譯成爲 字節碼
  6. 事實上 字節碼 依舊不能直接在 計算機中運行,而是需要 解釋器 便成 機器碼 才行
  7. 那麼 爲什麼 不直接轉爲 機器碼呢?
  8. 因爲 機器碼 佔用的內存實在是太多,在 移動端的場景之中,很快就會 把內存給吃掉
  9. 然後 在執行的 過程之中,V8 引擎 發現有一段代碼執行了多次,那麼 就會把這段代碼 單獨提出來,轉化成 字節碼,保存進內存中
  10. 這樣 你會發現,一段 js 代碼 會越來越快,瀏覽器佔用的 內存也會越來越高

5、把抽象的理論轉到可視化

前端就是可視化的直接呈現着,那麼有沒有辦法把上面的那些東西可視化呢?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function n() {
            var na = 'inner';
            return function () {
                debugger
                console.log(na)
            }
        }
        var fn = n()
        fn()

    </script>
</body>
</html>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章