文章目录
变量
ECMAScript变量是松散的,意思就是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意变量的命名占位符。在ECM6标准及以后,有三个关键字可以声明变量:var,let,const。
1.var关键字
要定义变量,可以使用var操作符,后面跟变量名。
例如:
var a;
var b = 1;
这行代码表示定义了一个名称为a的变量,在不初始化的情况下会保存一个特殊的值 undefined。我们也能够在定义变量的同时赋值
1.1.var声明作用域
使用 var 在函数中定义变量会让 var 成为该函数的局部变量。比如在一个函数内定义了一个 var 变量,在该函数执行完过后就会被销毁,var声明是不区分块作用域的,即在块作用域外是能够访问块作用域内的 var 声明的变量,块作用域对var没有任何影响(循环语句和分支语句是块作用域,所以在执行循环语句的时候用 var 来声明的结果可能和我们所想的有区别)。
function a(){
var num = 1;
}
console.log(num); // num is not defined
可以通过省略 var 操作符来定义全局变量,就算是在函数中定义,同样也会是全局变量,可以在函数外部访问到。
function a(){
num = 1;
}
console.log(num); // 1
注意
一般不推荐这么做,因为在局部作用域中定义全局变量很难维护,也会造成困惑。这是英文不能一下子断定省略var是不是有意为之。在严格模式(“use strict”) 下,如果想这样给未声明的变量赋值,则会抛出ReferenceError.
1.2.var声明提升
使用 var 时,下面的代码不会报错,这是因为使用这个关键字声明的变量会自动提升到函数作用域的顶部:
function a(){
console.log(age);
var age = 18;
}
a() // undefined
上述代码等同于:
function a(){
var age;
console.log(age);
age = 18;
}
a() //undefined
这就是所谓的 “提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部(实质就是js引擎的预编译,可参考我的另一篇文章)。此外,反复多次使用 var 来声明同一个变量也没有问题:
function a(){
var age = 10;
var age = 20;
var age = 30;
console.log(age)
}
a() // 30
2.let关键字
let 和 var 的作用差不多,但是有着非常重要的区别。最明显的区别是,let 声明的范围是块作用域,而var声明的范围是函数作用域。
var:
if(true){
var name = "abc";
console.log(name); // abc
}
console.log(name); // abc
let
if(true){
let name = "abc";
console.log(name); // abc
}
console.log(name); // ReferenceError: name没有被定义
name变量不能在 if 语句外使用,因为 let 的作用域仅在于该块内部。块作用域是函数作用域的子集,因此适用于var 的作用域限制同样也适用于 let
let 也不运行同一个块级作用域中出现冗余声明。这样会导致报错:
{
var name;
var name;
let a;
let a; // SyntaxError; 标识符a已经声明过了
}
对于声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。
var name;
let name; //SyntaxError
let age;
var age; //SyntaxError
2.1.暂时性死区
let 与 var 的另一个重要区别,就是 let 声明的变量不会在作用域中被提升。
先搞清楚值为undefined和报错是不同的概念,值为undefined表示已经对这个变量进行了预编译了,已经能够找到这个变量了,但是还没有对这个变量进行赋值,而报错则是js引擎目前没有找到这个值(预编译仅限于对 var 声明的变量和函数进行预编译)
// name会被提升
console.log(name); // undefined
var name = "lisi";
// age不会被提升
console.log(name); // ReferenceError:age 没有定义
let age = 18;
2.2.全局声明
与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为window对象的属性(var 声明的变量则会)。
var name = "abc";
console.log(window.name); // "abc"
let age = 18
console.log(window.age); // undefined
不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内延续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量
2.3.条件声明
在使用 var 声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的情况下声明它。
<script>
// 假设脚本不确定页面中是否已经声明了同名变量
// 那它可以假设没有声明过
var name = "abc";
// var 声明不需要检查,因为可以作为一个提升声明来处理,不需要检查之前是否做过同名声明
let age = "18";
// 如果age之前声明过,这里就会报错
// 条件声明的目的就是为了阻止这种错误,判断前面是否已经定义了let age
// 如果没有定义,再用let 来定义 age
</script>
一般判断错误的方法就是使用 try/catch 语句,但是由于 let 是块级作用域,用 try/catch 语句会导致该let声明仅限于该块
try{
console.log(age);
}catch(error){
// 判断出前面没有定义
let age; // 定义age
// 但是此age只适用于catch这个块作用域
}
所以,对于 let 这个新的ES6声明关键字,不能依赖条件声明关键字
注意:不能使用 let 进行条件式声明是一件好事,因为条件声明是一种反模式,它让程序变得更难理解。
2.4.for 循环中的 let 声明
在 let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:
for(var i=0;i<10;i++){}
console.log(i) // 10
改用let后这个问题就消失了:
for(let i=0;i<10;i++){}
console.log(i) // ReferenceError:i没有定义
在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:
for(var i = 0;i < 5;i++){
setTimeout(()=> console.log(i),0)
}
// 你可能会认为输出 0,1,2,3,4
// 实际上会输出 5,5,5,5,5
之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环推出的值 5.在之后执行超时逻辑时,所有的 i 都是同一个变量,所以输出的是同一个值
而在使用 let 声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量,也就是循环执行过程中每个迭代变量的值
for(let i =0;i<5;i++){
setTimeout(()=> console.log(i),0)
}
// 会输出0,1,2,3,4
其他循环语句同理
3.const声明
const 的行为与 let 基本相同,唯一一个重要的区别是同他声明的变量时必须同时初始化变量,并且尝试修改const声明的变量会导致运行时错误。
const 声明的限制值适用于它指向的变量的引用(可以理解为地址)。如果const变量引用的是一个对象,那么修改这个对象的内部的属性不违反const的限制
JavaScript引擎会为 for 循环中的 let 声明分别创建独立的变量实例。也就是说,每次迭代只是创建一个新的变量。这对 for-of 和 for-in 循环特别有意义
let i = 0;
for(const j = 6;i<5;i++){
console.log(j)
}
// 6,6,6,6,6
其实很好理解,把每次for循环当作新的块,const的作用域是块,所以这样声明是没有问题的。