我們在進到主題前先來看一段程式碼,隨後在開發人員工具中觀察執行過程
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)
用以下程式碼當範例
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 = '小明'
給覆蓋掉,
因此結果會顯示 '小明'
以上為今天的內容,感謝觀看