iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
自我挑戰組

重新開始 JavaScript系列 第 7

[Day7] 提升

何謂提升(Hoisting)?

提升(Hoisting) 其實主要是為了釐清 JavaScript 的運作所提出的觀念,在 ECMAScript 並未提出這個專有名詞,而所謂的提升,就是先將函式變數宣告先提到程式碼最上面的動作,再接著執行程式碼

執行環境的階段

JavaScript 會在執行環境中運行程式碼,而在建立一個執行環境時可以分為兩個階段:

  • 創造階段
    • 先將所有函式載入,且已可以運行
    • 再取出程式碼中的變數,並在記憶體上預留變數空間,此時不會賦予變數值,系統會先給予一個初始值 undefined
  • 執行階段
    • 依序執行程式碼,此時可賦予變數值

在創造環境階段優先把函式與變數的記憶體空間準備好,就叫做 提升 Hositing

變數的提升

同上面所述,在創造階段時,會將程式碼中所有變數先提到最前面,並在記憶體中預留空間給變數後,系統會先給予一個 undefined 的初始值,等到了執行階段,會依序執行剩下的程式碼,這時候才會賦予變數值,範例如下:

範例1:

var nickName = 'Carol';
console.log(nickName); // Carol

運行過程:

// 1. 創造階段

// 先宣告變數 nickName,在記憶體中預留空間,預設值為 undefined
var nickName;

// 2. 執行階段

// 賦予變數 nickName 值
nickName = 'Carol';

// 顯示變數 nickName 值
console.log(nickName); // Carol

範例2:

  • 第一個 console.log(nickName);
    在創造階段中,記憶體已預留空間給 變數 nickName,但還未賦予值,系統先給予一個預設值 undefined,所以顯示 undefined

  • 第二個 console.log(nickName);
    到了執行階段,程式碼會依序執行,所以到了第二個 console.log,已賦予 變數 nickName 一個值,顯示 Carol

console.log(nickName); // undefined
var nickName = 'Carol';
console.log(nickName); // Carol

運行過程:

// 1. 創造階段

// 先宣告變數 nickName,在記憶體中預留空間,預設值為 undefined
var nickName;

// 2. 執行階段

// 顯示變數 nickName 值
console.log(nickName); // undefined

// 賦予變數 nickName 值
nickName = 'Carol';

// 顯示變數 nickName 值
console.log(nickName); // 'Carol'

範例3:

變數 nickName 沒被建立或是宣告,所以系統找不到此變數,故顯示錯誤訊息 Uncaught ReferenceError: nickName is not defined

console.log(nickName);
// Uncaught ReferenceError: nickName is not defined

undefinedis not defined 差異可在 [Day6] 'undefined' vs 'not defined' 查看更多內容

函式的提升

關於函式的提升,首先我們要了解函式陳述式與函式表達式

函式陳述式

函式陳述式又稱為具名函式,他會直接給函式一個名子,並可直接利用此名子呼叫函式,而在創造階段時,函式陳述式優先,他會在記憶體中將函式陳述式的所有內容做保留

範例1:

function fn() { 
 // ...
}

fn();
運作過程:
// 1.創造階段
function fn() { 
 // ...
}

// 2.執行階段
fn();

範例2:

fn();
var nickName = 'Carol';
function fn() {
    console.log('我的暱稱叫 Carol');
}
var nickName = 'Mary';
fn();
運作過程:
// 1.創造階段
function fn() {
    console.log('我的暱稱叫 Carol');
}

// 2.執行階段
fn();

函式表達式

函式表達式可以將匿名函式或具名函式賦予至一個變數上。而函式表達式並不會像函式陳述式一樣,在創造階段就已經被記憶體保留空間,只會在創造階段時將此變數在記憶體中保留空間,直到執行階段才會將函式表達式賦予給此變數,所以若要利用函式表達式,就必須等函式已經賦予到變數上,才能運行此函式,因此只有函式陳述式會提升,而函式表達式則不會

若未定義函式名稱則為匿名函式,反之則為具名函式

範例1:

var fn = function () { 
 // ...
}

fn();
運作過程:
// 1.創造階段
var fn;

// 2.執行階段
fn = function () { 
 // ...
}

fn();

範例2:

console.log(fn); // undefined

var fn = function () {
    console.log('我的暱稱叫 Carol');
}

console.log(fn); // ƒ () { console.log('我的暱稱叫 Carol'); }

fn(); // 我的暱稱叫 Carol
運作過程:
// 1.創造階段

var fn;

// 2.執行階段

console.log(fn); // undefined

fn = function () {
    console.log('我的暱稱叫 Carol');
}

console.log(fn); // ƒ () { console.log('我的暱稱叫 Carol'); }

fn(); // 我的暱稱叫 Carol

範例3:

因匿名函式還未賦予到變數上,所以還不能運行此函式,故會報錯

fn();
var fn = function () {
    console.log('我的暱稱叫 Carol');
}
// Uncaught TypeErroe: fn2 is not a function

運作過程:
// 1.創造階段
var fn;

// 2.執行階段

fn();
// Uncaught TypeErroe: fn2 is not a function
// 報錯,所以下面的程式碼不會繼續執行

// fn2 = function () {
//     console.log('我的暱稱叫 Carol');
// }

混和例子練習

範例1:

fn(); // 第 1 名

function fn() {
    console.log('第 1 名');
}

fn(); // 第 1 名

var fn = function () {
    console.log('第 2 名');
}

fn(); // 第 2 名

運作過程:

// 1.創造階段
function fn() {
    console.log('第 1 名');
}

var fn;

// 2.執行階段
fn(); // 第 1 名

fn(); // 第 1 名

fn = function () {
    console.log('第 2 名');
}

fn(); // 第 2 名

範例2:

fn(); // 第 1 名

var fn = function () {
    console.log('第 2 名');
}

fn(); // 第 2 名

function fn() {
    console.log('第 1 名');
}

fn(); // 第 2 名

運作過程:

// 1.創造階段
function fn() {
    console.log('第 1 名');
}

var fn;

// 2.執行階段
fn(); // 第 1 名

fn = function () {
    console.log('第 2 名');
}

fn(); // 第 2 名

fn(); // 第 2 名

範例3:

後面函式覆蓋掉前面

fn(); // 第 2 名

function fn() {
    console.log('第 1 名');
}

fn(); // 第 2 名

function fn() {
    console.log('第 2 名');
}

fn(); // 第 2 名

運作過程:

// 1.創造階段
function fn() {
    console.log('第 1 名');
}
function fn() {
    console.log('第 2 名');
}

// 2.執行階段
fn(); // 第 2 名
fn(); // 第 2 名
fn(); // 第 2 名

範例4:

fn(); // undefined

function fn() {
    console.log(nickName);
}

var nickName = 'Carol';

fn(); // Carol

運作過程:

// 1.創造階段
function fn() {
    console.log(nickName);
}

var nickName;

// 2.執行階段
fn(); // undefined

nickName = 'Carol';

fn(); // Carol

範例5:

fn();
function fn() {
    if (rank) {
        rank = 2;
    }
}
console.log(rank); // undefined
var rank = 1;
console.log(rank); // 1
fn();
console.log(rank); // 2
// 1.創造階段
function fn() {
    if (rank) {
        rank = 2;
    }
}

var rank;

// 2.執行階段
fn();
console.log(rank); // undefined
rank = 1;
console.log(rank); // 1
fn();
console.log(rank); // 2

額外備註:

變數在宣告時,有一特性,如該變數已存在,則不會修正他 (只能用 var,因 letconst 不能重複宣告)

var a = 1;
var a;
console.log(a); // 1

參考文獻

六角學院 - JavaScript 核心篇

MDN - 提升(Hoisting)


上一篇
[Day6] 'undefined' vs 'not defined'
下一篇
[Day7] 提升
系列文
重新開始 JavaScript32

尚未有邦友留言

立即登入留言