提升(Hoisting)是 JavaScript 的概念,它使 JavaScript 與 Java 語(yǔ)言不同。在 Java 中,代碼中創(chuàng)建的每個(gè)變量都具有塊級(jí)作用域。意味著如果我們創(chuàng)建了任何將其可見(jiàn)性限制在聲明它的塊中的變量。因此,如果我們?cè)诼暶髦惺褂蒙厦娴淖兞?,則會(huì)出錯(cuò)。但是在 JavaScript 中,變量可以在聲明之前使用,這種機(jī)制稱為 Hoisted。這是 JavaScript 的默認(rèn)行為。
提升是 JS 的默認(rèn)行為,即在代碼執(zhí)行之前定義作用域頂部的所有聲明。提升的好處之一是它使我們能夠在函數(shù)出現(xiàn)在代碼中之前調(diào)用它們。JavaScript 只提升聲明,而不是初始化。
理解 JavaScript 提升變量究竟是什么聲明和初始化按以下順序進(jìn)行:
聲明 –> 初始化/賦值 –> 用法
// Variable lifecycle
let x; // Declaration
x = “hoisting”; // Assignment
console.log(x); // Usage
最重要的是,應(yīng)該始終記住 JavaScript 首先在后臺(tái)聲明變量。然后,初始化它們。因此,了解變量聲明的處理發(fā)生在任何代碼執(zhí)行之前也是很好的。但是,在執(zhí)行分配它們的代碼之前,JavaScript 中不存在未聲明的變量。因此,在執(zhí)行賦值時(shí),分配給未聲明變量的值會(huì)隱式地將其創(chuàng)建為全局變量。這指定所有未聲明的變量都是全局變量。
// hoisting
function Hoisting(){
x = 100;
let y = 200;
}
Hoisting();
console.log(x); // 100
console.log(y); // Reference Error: y is not defined
在上面的代碼示例中,有一個(gè)名為 Hoisting() 的函數(shù)。因此,我們有一個(gè)沒(méi)有使用 let/var/const 聲明的變量和一個(gè) let 變量 y。將未聲明的變量分配給全局作用域是由 JavaScript 完成的。但是對(duì)于變量 y,我們得到了一個(gè)引用錯(cuò)誤(?Refence Error
?)。
在函數(shù)作用域變量中托管
在 ES5 中,我們考慮 var 關(guān)鍵字。與 let/const 相比,使用 var 進(jìn)行提升有些不同。使用 var 來(lái)查看提升如何工作的示例:
var num (global)
console.log(car); // undefined
var car = ‘Lamborgini’;
在上面的代碼中,當(dāng)記錄在使用它之后聲明和分配的變量名稱時(shí),編譯器給出“undefined”的結(jié)果。這是意料之中的,因?yàn)槲覀儜?yīng)該在聲明它之前就嘗試使用 ?car
? 變量,因?yàn)槲覀儜?yīng)該得到 ReferenceError。但是解釋器對(duì)此有不同的看法,如下所示:
//how interpreter sees the above code
var car;
console.log(car); // undefined
car = ‘Lamborgini’;
let 和 const 關(guān)鍵字。
用 let 或 const 聲明的變量和常量不會(huì)被提升!
JavaScript 初始化不是初始化。
JavaScript 只能提升聲明,而不能用來(lái)初始化。
var a = “volkswagon”; // Initialize a
var b = “Lamborgini”; // Initialize b
elem = document.getElementById("car"); // Find an element
elem.innerHTML = a + " " + b; // Display a and b as volkswagon and lamborgini
在上面的代碼中,因?yàn)樽兞康穆暶靼l(fā)生在結(jié)果之前。所以結(jié)果,代碼的執(zhí)行打印了變量 a 和 b 的結(jié)果。
var a = “i10”; // Initialize a
elem = document.getElementById("car"); // Find an element
elem.innerHTML = "a is " + a + “ and b is " + b; // Display a and b
var b = “Lamborgini”; // Initialize b
結(jié)果:
a 是 ?i10
?而 b 是未定義的。
因此,這是因?yàn)橹话l(fā)生了聲明(?var b
?)的提升,而不是初始化(?=“Lamborgini”
?)到頂部。由于提升,b 在使用之前已經(jīng)聲明,但是因?yàn)槌跏蓟瘺](méi)有提升,所以 b 的值是未定義的。
提升類(Hoisting Classes)
JavaScript 類可以分為兩類:
- 類聲明
- 類表達(dá)式
在類聲明中,它們很像函數(shù)對(duì)應(yīng)物。因此,這意味著不會(huì)提升 JavaScript 類聲明。但是,它們?cè)谠u(píng)估之前保持未初始化狀態(tài)。因此,這實(shí)際上意味著必須先聲明一個(gè)類,然后才能使用它。
var car1 = new car();
car1.height = 5;
car1.weight = 500;
console.log(car1); // Output: ReferenceError: car is not defined
class car{
constructor(height, weight) {
this.height = height;
this.weight = weight;
}
}
在上面的代碼中,出現(xiàn)了引用錯(cuò)誤。這是因?yàn)樵??car1
?變量初始化之后,汽車類的定義就發(fā)生了。為了解決這個(gè)錯(cuò)誤,我們只需要在 ?car1
?初始化之前定義 ?car
?類。這是在類聲明中托管。
class car{
constructor(height, weight) {
this.height = height;
this.weight = weight;
}
}
var car1 = new car();
car1.height = 5;
car1.weight = 500;
console.log(car1);
因此,這給出了正確的結(jié)果。在 Class 表達(dá)式中它們很像它們的函數(shù)對(duì)應(yīng)物。因此,這意味著沒(méi)有提升類表達(dá)。因此,下面是類表達(dá)式的未命名或匿名變體的示例:
var rect = new shapes();
rect.height = 10;
rect.width = 20;
console.log(rect); // Output: TypeError: shapes is not a constructor
var shapes = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
Thus, the correct way to do it is like this:
var shapes = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
var rect = new shapes();
rect.height = 10;
rect.width = 20;
console.log(rect);
總結(jié)
對(duì)于許多開(kāi)發(fā)人員來(lái)說(shuō),提升是 JavaScript 的一種未知行為。許多開(kāi)發(fā)人員也忽略了它的重要性。此外,如果開(kāi)發(fā)人員不了解提升,程序可能包含 bug(錯(cuò)誤)。為了避免 bug,請(qǐng)始終在每個(gè)作用域的開(kāi)頭聲明所有變量。因此,這就是 JavaScript 解釋代碼的方式,這始終是一個(gè)很好的規(guī)則。