目录
ECMAScript变量是松散类型的,意思就是可以用于保存任何类型的数据。每个人变量只不过是一个用于保存任意值的命名占位符。var在ECMAScript的所有版本中都可以使用,而let、const只能在ECMAScript6及更高的版本中使用。
1.var关键字
1.1var声明作用域
使用var操作符定义的变量会成为包含它的函数的局部变量。
<script>
function test() {
var message = "ji"; //局部变量
}
test();
console.log(message); //出错
</script>
使用var在一个函数内部声明变量也就意味着该变量将在函数退出时被销毁
当调用test()时会创建这个变量并给它赋值,调用过后变量随即被销毁,所以当你在函数外部再次console.log(message);输出时会报错
但是如果在函数内部省略var这个操作符,则可以创建一个全局变量,只要调用一次函数test(),就会定义这个变量,并且可以在函数外部被访问到,自然也就不会报错
虽然可以通过省略 var 操作符来定义全局变量,但是不推荐这样做。在局部作用域中定义的全局变量很难维护,也会造成困惑,我们不能一下子就断定省略var是不是有意而为之
在 use strict 模式下,如果这样做将会抛出 ReferenceError
<script>
function test() {
message = "ji"; //局部变量
}
test();
console.log(message); //ji
</script>
1.2var声明提升
<script>
function foo() {
console.log(age);
var age = 26;
}
foo(); //undefined
//等价于
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); //undefined
</script>
使用 var 时,上面的代码并不会报错,这是因为使用这个关键字声明的变量会自动提升到函数作用域的顶部
注意:
- 变量声明只提升变量,不会提升变量值!!!
- 变量声明提升就是把所有变量都拉到函数作用域的顶部
-
var、let声明的就是变量,变量一旦初始化之后,还可以重新赋值
反复多次使用var声明同一个变量也可以
<script>
function foo() {
var age = 12;
var age = 15;
var age = 45;
console.log(age); //45
}
foo();
</script>
2. let声明
let声明的范围是块作用域,而var声明的范围是函数作用域
<script>
if (true) {
var name = 'jack';
console.log(name); //jack
}
console.log(name); //jack
</script>
<script>
if (true) {
let age = 26;
console.log(age); //26
}
console.log(age); //
</script>
这里age变量之所以不能在if块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集。因此使用于var的作用域限制同样也适用于let
let也不允许同一个块作用域中出现冗余声明:
<script>
var name;
var name;
console.log(name);
let age;
let age;
console.log(age);
</script>
当然JavaScript引擎会记录用于变量声明的标示符及其所在的块作用域,因此嵌套使用的、相同的标示符不会报错,而这是因为同一个块中没有重复声明:
<script>
var name = 'jack';
console.log(name); //jack
if (true) {
var name = 'marry';
console.log(name); //marry
}
let age = 30;
console.log(age); //30
if (true) {
let age = 21;
console.log(age); //21
}
</script>
2.1暂时性死区
let与var的另一个重要的区别就是let声明的变量不会在作用域中被提升:
<script>
console.log(name);
var name = 'jack'; //undefined
console.log(age);
let age = 25;
</script>
在代码解析时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用为声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”,在此阶段引用任何后面才声明的变量都会抛出ReferenceError错误。
常见的块级作用域:
{}
for(){}
while(){}
do{}while()
if(){}
switch(){}
function(){}
const person = {
getAge: function () {}
};
2.2全局声明
与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会):
<script>
var name = 'jack';
console.log(window.name); //jack
let age = 24;
console.log(window.age); //
</script>
不过声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复生命同一个变量。
2.3条件声明
在使用var声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量名,同时也就不可能在没有声明的情况下声明它。
<script>
var name = 'jack';
let age = 26;
</script>
<script>
//假设脚本不确定页面中是否已经声明了同名变量
//那它可以假设还未声明过
var name = 'marry';
//这里没有问题,因为可以被作为一个提升声明来处理,不需要检查之前是否已经声明过同名变量
let age = 30;
//如果之前声明过,那么这里就会报错
</script>
使用tyr/catch语句或typeof操作符也不能解决,因为条件块中let声明的作用域仅限于该块:
<script>
var name = 'jack';
let age = 26;
</script>
<script>
//假设脚本不确定页面中是否已经声明了同名变量
//那它可以假设还未声明过
if (typeof name === 'undefined') {
let name;
}
//name被限制在if {} 块的作用域内,因此这个赋值形同于全局赋值
name = 'marry';
try {
console.log(age); //如果age没有声明过,则会报错
} catch (error) {
let age;
}
//age被限制在catch {} 块的作用域内,因此这个赋值形同全局赋值
age = 30;
</script>
2.4 for循环中的let声明
在let出现之前,for循环定义的迭代变量会渗透到循环体外部:
<script>
for (var i = 0; i < 5; i++) {
//循环逻辑
}
console.log(i); //5
</script>
改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环块内部:
<script>
for (let i = 0; i < 5; i++) {
//循环逻辑
}
console.log(i); //
</script>
在使用var的时候,最常见的问题就是对迭代的奇特声明和修改:
例子1:
<script>
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
}
</script>
例子2:
<script>
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0)
}
</script>
3.const声明
const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行错误:
<script>
const age = 21;
age = 25;
</script>
const也不允许重复声明:
<script>
const age = 21;
const age = 25;
</script>
const声明的作用域也是块:
<script>
const age = 21;
if (true) {
const age = 54;
}
console.log(age);
</script>
const声明的限制只适用于它指向的变量的引用,换句话说,如果const变量的引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制:
<script>
const person = {};
person.name = 'marry';
console.log(person.name);
</script>
JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增):
<script>
for (const i = 0; i < 10; i++) {}
</script>
不过,如果你只想用const声明一个不会被修改的循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对for-of和for-in循环特别有意义:
<script>
let i = 0;
for (const j = 7; i < 5; i++) {
console.log(j);
}
for (const key in {
a: 1,
b: 2,
c: 3
}) {
console.log(key);
}
for (const value of[1, 2, 3, 4, 5]) {
console.log(value);
}
</script>
注意:
-
const 声明的就是常量,常量一旦初始化,就不能重新赋值了,否则就会报错
4.声明风格及最佳实践
1.不使用var
有了let和const后,限制只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置、以及不变的值
2.const优先、let次之
使用const声明可以让浏览器运行时强制保持质量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。