【閱文筆記】提升【你不知道的JavaScript(上)】

Milk595發表於2020-12-16

提升

函式作用域和塊作用域的行為是一樣的, 可以總結為: 任何宣告在某個作用域內的變數, 都將附屬於這個作用域。但是作用域同其中的變數宣告出現的位置有某種微妙的聯絡,這正是提升

一、先有雞還是先有蛋

直覺上會認為 JavaScript 程式碼在執行時是由上到下一行一行執行的。 但實際上這並不完全正確, 有一種特殊情況會導致這個假設是錯誤的。

考慮下面程式碼:?

a = 2;
var a;
console.log( a );

你認為 console.log(…) 宣告會輸出什麼呢?

很多開發者會認為是 undefined, 因為 var a 宣告在 a = 2 之後, 他們自然而然地認為變數被重新賦值了, 因此會被賦予預設值 undefined。 但是, 真正的輸出結果是 2

考慮另外一段程式碼:?

console.log( a );

var a = 2;

在這裡插入圖片描述
那麼到底發生了什麼? 看起來我們面對的是一個先有雞還是先有蛋的問題。 到底是宣告(蛋) 在前, 還是賦值(雞) 在前?

二、編譯器再度來襲

為了搞明白這個問題,回憶一下, 引擎會在解釋 JavaScript 程式碼之前首先對其進行編譯。 編譯階段中的一部分工作就是找到所有的宣告, 並用合適的作用域將它們關聯起來,這個機制, 也正是詞法作用域的核心內容。

正確的思考思路是, 包括變數和函式在內的所有宣告都會在任何程式碼被執行前首先被處理

【當你看到 var a = 2; 時, 可能會認為這是一個宣告。 但 JavaScript 實際上會將其看成兩個宣告: var a; 和 a = 2;。 第一個定義宣告是在編譯階段進行的第二個賦值宣告會被留在原地等待執行階段

第一段程式碼進行如下處理?:

var a;
a = 2;
console.log( a );

其中第一部分是編譯, 而第二部分是執行

第一段程式碼進行如下處理?:

var a;
console.log( a );
a = 2;

因此, 打個比方, 【這個過程就好像變數和函式宣告從它們在程式碼中出現的位置被“移動”到了最上面。 這個過程就叫作提升

換句話說, 先有蛋(宣告) 後有雞(賦值)

只有宣告本身會被提升, 而賦值或其他執行邏輯會留在原地

foo();

function foo() {
	console.log( a ); // undefined
	var a = 2;
}

foo 函式的宣告(這個例子還包括實際函式的隱含值) 被提升了, 因此第一行中的呼叫可以正常執行

注意每個作用域都會進行提升操作

儘管前面大部分的程式碼片段已經簡化了(因為它們只包含全域性作用域), 而我們正在討論的 foo(..) 函式自身也會在內部對 var a 進行提升(顯然並不是提升到了整個程式的最上方)。 因此這段程式碼實際上會被理解為下面的形式?:

function foo() {
	var a;
	console.log( a ); // undefined
	a = 2;
}

foo();

可見, 函式宣告會被提升, 但是函式表示式卻不會被提升
在這裡插入圖片描述
同時也要記住, 即使是具名的函式表示式, 名稱識別符號在賦值之前也無法在所在作用域中使用

在這裡插入圖片描述

三、函式優先

函式宣告和變數宣告都會被提升。但是函式會首先被提升, 然後才是變數

考慮以下程式碼:

foo(); // 1
var foo;
function foo() {
  console.log( 1 );
} 

foo = function() {
  console.log( 2 );
};

會輸出 1 而不是 2 ! 這個程式碼片段會被引擎理解為如下形式:

function foo() {
  console.log( 1 );
} 
foo(); // 1
foo = function() {
  console.log( 2 );
};

注意, var foo 儘管出現在 function foo()… 的宣告之前, 但它是重複的宣告(因此被忽略了), 因為函式宣告會被提升到普通變數之前

在這裡插入圖片描述

總結:

我們習慣將 var a = 2; 看作一個宣告, 而實際上 JavaScript 引擎並不這麼認為。 它將 var a和 a = 2 當作兩個單獨的宣告, 第一個是編譯階段的任務,而第二個則是執行階段的任務

這意味著無論作用域中的宣告出現在什麼地方, 都將在程式碼本身被執行前首先進行處理。可以將這個過程形象地想象成所有的宣告(變數和函式) 都會被“移動” 到各自作用域的最頂端, 這個過程被稱為提升

宣告本身會被提升, 而包括函式表示式的賦值在內的賦值操作並不會提升

要注意避免重複宣告, 特別是當普通的 var 宣告和函式宣告混合在一起的時候, 否則會引起很多危險的問題!

相關文章