提升(Hoisting)是 JavaScript 的概念,它使 JavaScript 与 Java 语言不同。在 Java 中,代码中创建的每个变量都具有块级作用域。意味着如果我们创建了任何将其可见性限制在声明它的块中的变量。因此,如果我们在声明中使用上面的变量,则会出错。但是在 JavaScript 中,变量可以在声明之前使用,这种机制称为 Hoisted。这是 JavaScript 的默认行为。
提升是 JS 的默认行为,即在代码执行之前定义作用域顶部的所有声明。提升的好处之一是它使我们能够在函数出现在代码中之前调用它们。JavaScript 只提升声明,而不是初始化。
理解 JavaScript 提升变量究竟是什么声明和初始化按以下顺序进行:
声明 –> 初始化/赋值 –> 用法
// Variable lifecycle
let x; // Declaration
x = “hoisting”; // Assignment
console.log(x); // Usage
最重要的是,应该始终记住 JavaScript 首先在后台声明变量。然后,初始化它们。因此,了解变量声明的处理发生在任何代码执行之前也是很好的。但是,在执行分配它们的代码之前,JavaScript 中不存在未声明的变量。因此,在执行赋值时,分配给未声明变量的值会隐式地将其创建为全局变量。这指定所有未声明的变量都是全局变量。
// hoisting
function Hoisting(){
x = 100;
let y = 200;
}
Hoisting();
console.log(x); // 100
console.log(y); // Reference Error: y is not defined
在上面的代码示例中,有一个名为 Hoisting() 的函数。因此,我们有一个没有使用 let/var/const 声明的变量和一个 let 变量 y。将未声明的变量分配给全局作用域是由 JavaScript 完成的。但是对于变量 y,我们得到了一个引用错误(Refence Error
)。
在函数作用域变量中托管
在 ES5 中,我们考虑 var 关键字。与 let/const 相比,使用 var 进行提升有些不同。使用 var 来查看提升如何工作的示例:
var num (global)
console.log(car); // undefined
var car = ‘Lamborgini’;
在上面的代码中,当记录在使用它之后声明和分配的变量名称时,编译器给出“undefined”的结果。这是意料之中的,因为我们应该在声明它之前就尝试使用 car
变量,因为我们应该得到 ReferenceError。但是解释器对此有不同的看法,如下所示:
//how interpreter sees the above code
var car;
console.log(car); // undefined
car = ‘Lamborgini’;
let 和 const 关键字。
用 let 或 const 声明的变量和常量不会被提升!
JavaScript 初始化不是初始化。
JavaScript 只能提升声明,而不能用来初始化。
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
在上面的代码中,因为变量的声明发生在结果之前。所以结果,代码的执行打印了变量 a 和 b 的结果。
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
结果:
a 是 i10
而 b 是未定义的。
因此,这是因为只发生了声明(var b
)的提升,而不是初始化(=“Lamborgini”
)到顶部。由于提升,b 在使用之前已经声明,但是因为初始化没有提升,所以 b 的值是未定义的。
提升类(Hoisting Classes)
JavaScript 类可以分为两类:
- 类声明
- 类表达式
在类声明中,它们很像函数对应物。因此,这意味着不会提升 JavaScript 类声明。但是,它们在评估之前保持未初始化状态。因此,这实际上意味着必须先声明一个类,然后才能使用它。
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;
}
}
在上面的代码中,出现了引用错误。这是因为在 car1
变量初始化之后,汽车类的定义就发生了。为了解决这个错误,我们只需要在 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);
因此,这给出了正确的结果。在 Class 表达式中它们很像它们的函数对应物。因此,这意味着没有提升类表达。因此,下面是类表达式的未命名或匿名变体的示例:
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);
总结
对于许多开发人员来说,提升是 JavaScript 的一种未知行为。许多开发人员也忽略了它的重要性。此外,如果开发人员不了解提升,程序可能包含 bug(错误)。为了避免 bug,请始终在每个作用域的开头声明所有变量。因此,这就是 JavaScript 解释代码的方式,这始终是一个很好的规则。