JS函數名與變量名重名的問題
由於JavaScript的預編譯會導致變量聲明提升,聲明式函數整體提升,因此當函數名與變量名重名的時候將會產生問題。
變量以及變量表達式(包括函數的表達式聲明)在預編譯階段都只是聲明提升,而沒有賦值,值都爲undefined。
而聲明式函數在預編譯階段將會整體提升。
而在編譯過程中,提升也是有順序的:參數 > 函數 > 變量
因此函數與變量的預編譯遵循以下規則:
- 函數聲明置頂;
- 變量聲明置頂;
- 遇到同名時,變量聲明比函數聲明更優先(置頂);
- 變量不會重複聲明;
- 直接變量聲明時賦值,在預編譯解析時,會拆成聲明和賦值2個部分,聲明會置頂,而賦值將保留在原來的位置;
- 聲明的提升只會提升到當前作用域下的頂層,而不會提升到作用域之外。
- 預編譯過程中形成的作用域鏈的查找順序是由裏向外,JS執行順序是自上往下的。
- 當函數被聲明在判斷語句內部時,JS會將函數聲明當成表達式(賦值聲明),即不會提升
//變量或變量表達式聲明提升
var a = 10;
//這相當於
var a; //這就是發生在預編譯階段的變量的聲明提升,在這個階段變量只有聲明,而沒有賦值,爲undefined
a = 10; //這是在執行階段,將會對變量賦值
//函數表達式
let fun = function(){
console.log('hello simon');
}
//這相當於
let fun; //這就是發生在預編譯階段的變量的聲明提升,在這個階段變量只有聲明,而沒有賦值,爲undefined
fun = function(){ //執行階段纔會進行定義
console.log('hello simon');
}
//聲明式函數整體提升
var a = 10;
function fun(){
var b = 11;
}
//這相當於
function fun(){
var b;
b = 11;
}
var a;
a = 10;
題1
由於函數聲明是整體提升的,因此下面代碼中,a函數聲明在前,而變量a賦值在後,因此a是一個變量,而不是一個函數了,a執行出錯
var a = 10;
function a(){
console.log(a);
}
a();
//相當於
var a = function(){
console.log(a);
};
a = 10;
a(); //將報錯,此時a是一個變量了
題2
預編譯過程中形成的作用域鏈的查找順序是由裏向外,JS執行順序是自上往下的。因此下面代碼中a = 1時,if的作用域中並不存在a變量,所以會將1賦值給全局中的a變量。
var a = 0;
if(true){
a = 1; //作用域中不存在a變量,因此會通過作用域鏈向外查找,找到全局中的a變量,並將1賦值
function a(){} //這是函數聲明,相當於定義了一個函數a,並給其分配了內存
a = 21; //此時作用域中已經存在了a變量(a函數),因此該賦值將直接賦值給作用域中的a而不是全局的a
console.log('裏面', a); //21,由於作用域中存在a變量了,因此直接打印作用域中的a
}
console.log('外面', a); //1,全局作用域中存在a變量,並賦值爲1,因此打印1
//相當於
var a;
a = 0;
if(true){
a = 1;
function a(){} //由於函數是定義在判斷語句中,所以沒有聲明提升
a = 21;
console.log('裏面', a) //21
}
console.log('外面', a); //1
題3
函數會整體提升,因此b函數會被提升到全局作用域中的頂部,而a函數被提升到b函數作用域頂部。注意函數是自上而下執行的
var a = 1;
function b(){
a = 10;
return;
function a(){
console.log(a);
}
}
b();
console.log(a);
//相當於
function b(){ //整體提升
function a(){ //整體提升
console.log(a);
}
a = 10;
return;
}
var a;
a = 1;
b(); //執行b函數後,內部函數a會聲明,因此作用域內存在一個a變量(函數),a = 10會賦值給a函數,而不是全局中的a變量了
console.log(a); //1 打印的是全局的a變量,內部的a變量無法獲取到