Try   HackMD

JS 核心 提升 (hoisting)

提升 (hoisting)

先來說明執行環境是如何解析程式碼的

  • 創造環境:var a; 記憶體會先把 a 記起來,此時若去查找 a 會出現 undefined。
  • 執行(賦值): a = 1; 此時才會把 1 這個值賦予給 a。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

函式陳述式在創造階段就會優先載入,可以運行

使用函式陳述式在宣告變數時,在創造階段就會把整個函式給載入進來。
這樣做的優點是:可以在程式碼宣告該函式之前使用它。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 (函式陳述式) : 函式在創造階段時就已經載入,可以正常運行。像這種執行函式先而宣告函式在後,稱為函式提升。

callName();
function callName() {
  console.log('呼叫小明');  // 呼叫小明
}

拆解 : 函式陳述式在創造階段,記憶體空間已包含函式完整內容了。

//創造階段 function callName() { console.log('呼叫小明'); } //執行 callName();

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 (函式表達式)

callName( ); 可以在表達式之後呼叫

var callName = function(){
  console.log('呼叫小明');
}
callName();  // 呼叫小明

若 callName( ); 在表達式之前呼叫,會跳錯

console.log(callName);  // undefined
callName();             // callName is not a function
var callName = function(){
  console.log('呼叫小明');
}

拆解

// 創造階段 var callName; // 先準備一個記憶體空間,但沒有值,屬於 undefined // 執行 callName = function(){ // 執行時,才把 function() 賦予到此變數上 console.log('呼叫小明'); // callName 尚在未賦值階段,所以會跳錯 } callName();

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例 (創造階段,函式優先)

// 陳述式 與 表達式
function callName() {
  console.log('呼叫小明 1');
}
var callName = function () {
  console.log('呼叫小明 2');
}
callName();   // 呼叫小明 2

拆解

  1. 函式callName被往前移,在創造階段時函式callName先被分配到記憶體中的一個位置,且函式的陳述式已被載入。
  2. 接著才是宣告變數callName。
  3. 進入創造階段,callName的值被新的函式陳述式覆蓋掉,因此結果會是呼叫小明2。
// 創造階段 function callName() { console.log('呼叫小明 1'); } var callName; // 執行,賦予值 callName = function () { console.log('呼叫小明 2'); } // 此時變數 callName 被函式覆蓋過去 callName(); // 呼叫小明 2

試著把 (8行) callName( ); 往前移

// 創造階段 function callName() { console.log('呼叫小明 1'); } var callName; // 執行,賦予值 callName(); // 呼叫小明 1 (因為變數重複宣告是沒有用的) callName = function () { console.log('呼叫小明 2'); }

變數 vs 函式的拉升

變數與函式的拉升的不同之處在於,變數的拉升只有宣告部份,而函式的拉升是整個函式,因此函式在宣告前是可以執行的。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

callName();  // undefined
function callName() {
    console.log(Ming); 
}
var Ming = '小明';

拆解

// 創造階段
function callName() {
  console.log(Ming); 
}
var Ming;

// 執行,賦予值
callName();  // 目前此函式所取的值是全域變數的Ming,此時Ming尚未被賦予值,所以為undefined
Ming = '小明';

若同時有多個函式同名,則後面的會覆寫前面的宣告

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

function callName() {
  console.log('小明');
}
callName();

function callName() {
  console.log('杰倫');
}
callName();

拆解 : 兩個函式名稱都相同,後面會覆蓋前面的 ; 執行時只會取到 "杰倫"

// 創造階段
function callName() {
  console.log('小明');
}
function callName() {
  console.log('杰倫');
}

// 執行,賦予值
callName();  // 杰倫
callName();  // 杰倫

若函式和變數同名,則函式會優先

範例如下,同名函式 foo 和變數 foo,由於函式優先,因此 foo() 得到 1 而非 undefined() 的結果 TypeError。

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

拆解

// 創造階段
function foo() {
  console.log(1);
}
// 執行,賦予值
foo(); // 1
foo = 2;

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
範例

whosName();
function whosName() {
  if (name) {
      name = '杰倫';
  }
}

var name = '小明';
console.log(name);  // 小明

拆解

// 創造階段
function whosName() {  // whosName() 因函式優先被提升到最上方
  if (name) {
      name = '杰倫';
  }
}
var name;  // 宣告一個全域變數name,此時尚未被賦於值(顯示undifined)

// 執行,賦予值
whosName();         
// whosName();在進入if判斷式之前,並沒有宣告變數name,因此向外尋找。
// 此時全域變數name還未被賦予值,if判斷結果為false,函式執行中斷並跳出。
name = '小明';       //  name 被重新賦予值 小明
console.log(name);  // 答案為小明

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
學習回顧

  • 變數與函式陳述式,在進入執行階段前,其實就已經完成宣告。這種「將變數宣告與函式宣告的動作,提升到程式碼最頂端」的行為,就是 Hoisting(提升)。
  • 拉升是逐範疇的,在函式內宣告變數,不會被拉升到全域範疇而成為全域變數。
  • 函式陳述式在創造階段就會優先載入,可以運行。
  • 函式表達式不會被提升,因此若在函式表達式之前呼叫,它就會出現錯誤訊息。
  • 變數與函式的拉升的不同之處在於,變數的拉升只有宣告部份,而函式的拉升是整個函式,因此函式在宣告前是可被執行的。
  • 若函式和變數同名,則函式會優先;若同時有多個函式同名,則後面的會覆寫前面的宣告。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
相關參考文件