iT邦幫忙

2021 iThome 鐵人賽

DAY 6
1
自我挑戰組

JavaScript 核心觀念系列 第 6

【Day06】提升(Hoisting)

  • 分享至 

  • xImage
  •  

我們在進到主題前先來看一段程式碼,隨後在開發人員工具中觀察執行過程

function doSomething(){
    var mom = '老媽';
}

doSomething();

首先是函式執行前,這沒特別的問題,繼續往下看

執行 doSomething() 後,

我們可以看到在 doSomething 作用域中 mom 值為 undefined

再往下一部時,能看到 mom 被賦予 '老媽' 這個值

我們再來看另一個範例

var ming = '小明';
console.log(ming);

此時會回傳 '小明'

如果將 console.log() 放在前面

console.log(ming);
var ming = '小明';

這時就會回傳 undefined

當我們將宣告的變數 ming 拿掉時,

console.log(ming);

此時會出現 ReferenceError: ming is not defined

為何會有這樣的結果呢?

那是因為程式碼在執行時會分為兩個階段

首先會先宣告變數,之後才將值賦予到變數中

如下所示

var ming;

ming = '小明';

console.log(ming);

此時結果也是 '小明'

而為什麼現有這種情況產生,就該進到我們的主題了

提升(Hoisting)

在前幾天的文章中有提到執行環境,

而在建立執行環境時有兩個階段

分別為創造環境執行這兩個階段

在創造環境時會將所有變數提出來,在記憶體上建立一個空間,

到了執行階段時,才將時賦予到變數中,

在創造環境把記憶體空間準備好的流程,稱為提升(Hoisting)

用以下程式碼當範例

var a = 'weiwei';

我們先假設記憶體是一對的,

左邊放 key,右邊放值

在創造環境中,

記憶體左邊會代入 a 這個變數,

而在創造環境中,還不會把值賦予到變數中,

因此記憶體右邊會是 undefined

到了執行階段時,

才將值代入變數中,

因此記憶體右邊會是 'weiwei'

但如果使用函式陳述式宣告一個變數時,

在創造環境時就會將整個函式載入進去,

和變數宣告有些許不同

以圖表示

在創造環境時,

變數 a 的值還沒被載入,但函式的值已經被載入進去了,

而變數 a 要到執行階段才會將值代入

我們來看範例

範例一

我們來看文章開頭範例

var ming = '小明';

console.log(ming);

程式碼在運行時,會解析為下方的結果

var ming;  // 創造階段

ming = '小明';  // 執行

console.log(ming);

宣告變數就稱為創造階段,

而開始賦予值之後稱為執行階段

範例二

函式陳述式範例

function callName() {
    console.log('呼叫 weiwei')
}

callName();

此時會回傳 '呼叫 weiwei'

當我們把 callName() 放在函式前面時

callName();

function callName() {
    console.log('呼叫 weiwei')
}

此時的結果也是 '呼叫 weiwei'

為何會這樣呢?

我們來拆解這段程式碼

// 創造階段
function callName() {
    console.log('呼叫 weiwei')
}

// 執行
callName();

因為在創造階段時,

其記憶體空間就已經包含完整內容,

所以就算在宣告函式之前執行函式,

也會獲得相同的結果

範例三

函式表達式範例

var callName = function() {
    console.log('呼叫 weiwei')
}

callName();

此時函式結果為 '呼叫 weiwei'

但當我們把 callName() 移到宣告函式前面時

callName();

var callName = function() {
    console.log('呼叫 weiwei')
}

此時會出現 callName is not a function

這時我們使用 console.log()callName 的值是什麼

console.log(callName);

var callName = function() {
    console.log('呼叫 weiwei')
}

這時會看到 undefined

我們來解析這段程式碼

// 創造階段
var callName;

// 執行
callName = function() {
    console.log('呼叫 weiwei')
}

在創造階段會先準備 callName 這個變數的記憶體空間,

但還沒被賦予值,

到了執行階段時,才將函式賦予到 callName 這個變數上,

因此如果要運行函式表達式中的函式時,

必須在賦予完值後,才能運行該函式

範例四

function callName() {
    console.log('呼叫 weiwei 1');
}

var callName = function() {
    console.log('呼叫 weiwei 2');
}

callName();

該程式碼結果為 '呼叫 weiwei 2'

當我們將函式的表達式與陳述式互換位置時,

var callName = function() {
    console.log('呼叫 weiwei 2');
}

function callName() {
    console.log('呼叫 weiwei 1');
}

callName();

此時結果也是 '呼叫 weiwei 2'

為何會這樣呢?

那是因為在創造階段時,函式是優先的

我們來解析這段程式碼

// 函式優先
// 創造階段
function callName() {
    console.log('呼叫 weiwei 1');
}
var callName
// 執行
callName = function() {
    console.log('呼叫 weiwei 2');
}
callName();

在創造階段因為函式優先所以函式放前面,變數放後面

而在執行階段時,變數被另一個函式蓋掉,因此會顯示 '呼叫 weiwei 2'

如果將 callName() 往前移

// 函式優先
// 創造階段
function callName() {
    console.log('呼叫 weiwei 1');
}
var callName
// 執行
callName();
callName = function() {
    console.log('呼叫 weiwei 2');
}

這時結果會呈現 '呼叫 weiwei 1'

範例五

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

該範例結果為 undefined

為何不會出現錯誤呢?

我們來解析這端程式碼

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

在創造階段 callName() 中的 ming 會向外查找全域中的值,

而全域中的 ming 此時為 undefined

因此到了執行階段執行 callName() 時會回傳 undefined

範例六

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

callName();  // 第一次執行

function callName() {
    console.log('weiwei')
}

callName();  // 第二次執行

此時兩個結果都是 'weiwei'

為何會這樣呢?

我們來看解析

// 創造階段
function callName() {
    console.log('小明')
}
function callName() {
    console.log('weiwei')
}
// 執行
callName();  // 第一次執行
callName();  // 第二次執行

因為後面的結果會覆蓋掉前面的,

因此顯示的結果都是 'weiwei'

範例七

whoName();

function whoName() {
    if (name) {
        name = 'weiwei';
    }
}

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

此時結果為 '小明'

來看解析

// 創造階段
function whoName() {
    if (name) {
        name = 'weiwei';
    }
}
var name;
// 執行
whoName();
name = '小明';
console.log(name);

在創造階段中 name 的值為 nudefined

在執行階段中,

whoName() 不管 name 值是什麼,

都會被後面的 name = '小明' 給覆蓋掉,

因此結果會顯示 '小明'

以上為今天的內容,感謝觀看


上一篇
【Day05】範圍鍊(Scope Chain)
下一篇
【Day07】記憶體存放與釋放
系列文
JavaScript 核心觀念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言