本文搬运的是我自己的博客,有兴趣可以移步至:总结JavaScript中的var、let、const的区别
ES6以前JS只能使用var声明变量,ES6之后更新了let与const,我参照了**《JavaScript高级程序设计》第四版**对var、let和const的区别做了一个简单的总结,在这里分享给大家。第一次写博客,如有错漏,欢迎大家批评指正。(๑•̀ㅁ•́ฅ)
var
关于var为什么会出现变量提升,在全局定义时会被挂在到window对象上,我在函数执行与作用域链这篇文章中有非常详尽的解释。如果你对底层原理感兴趣可以看一下。如果你还没有了解到什么是var的变量提升、作用域等问题,那么可以忽略我这段内容,自细阅读下面的内容即可。
1、松散类型
ECMAScript是松散类型,变量可以用于保存任何数据类型
var a = 'string';
console.log(a);
a = 1;
console.log(a);
a = true;
console.log(a)
2、声明初始化undefined
声明的变量初始化值是undefined
var a;
consloe.log(a)
3、声明作用域
var作用域是函数作用域,也就是说它的作用范围是包含它的那个函数。
function say(){
var a = 'hello';
console.log(a);
}
console.log(a);
4、错误定义方式导致的全局污染
函数中声明一个变量却没有使用var时会变成全局变量,可以看到下面代码中的message在函数作用域外被访问到了
function test(){
message = 'test';
}
test();
console.log(message);
上面代码等同于下面代码:
var message;
function test(){
message = 'test'
}
test();
console.log(message);
5、变量提升
使用var时下面代码不是报错,因为var的变量声明会自动提升到函数作用域的顶部
function foo(){
console.log(foo);
var foo = 'foo';
}
foo();
// 输出undefined
上面代码之所以输出undefined,是因为ECMAScript在运行时将它等价于了下面代码
function foo(){
var foo;
console.log(foo);
foo = 'foo';
}
foo();
所谓的变量提升就是把所有的变量声明都提升到函数的做顶部,除此之外,即使你声明很多个相同的变量也不会报错
function foo(){
var age = 12;
var age = 18;
var age = 21;
var age = '32';
console.log(age);
}
foo();
let
let跟var的作用差不多,但是有着非常重要的差别。
1、作用域
块级作用域(var是函数作用域)
if(true){
var name1 = 'var';
console.log(name1); // var
}
console.log(name1); // var
if(true){
let name2 = 18;
console.log(name2); // 18
}
console.log(name2); // name is not defind
之所以找不到name2是因为let声明的变量作用域是块级的,在这个块运行结束后会自动销毁。
2、冗余声明
let不允许在同一作用域中冗余声明(就是重复声明),这样会导致报错
var name;
var name;
let age;
let age;//SyntaxError: 标识符age已经声明过了。
冗余声明不会因为混用var和let而受影响,这两个关键字定义的并不是不同的变量,它们指出的是变量在相关作用域如何存在
var name;
let name;//SyntaxError
let age;
let name;//SyntaxError
嵌套使用相同的标识符不会报错,因为它们属于不同的块
var name = 'var';
console.log(name); //var
if(true){
var name = 'matt';//matt
console.log(name);
}
let age = 18;
console.log(age); //18
if(true){
let age = 89;
console.log(age);//89
}
3、暂时性死区
let与var的一个重要区别就是不存在变量提升
console.log(name)
var name = '苦哈哈';
console.log(age); //ReferenceError: age 没有定义
let age = 18;
暂时性死区: JavaScript在解析代码时也会注意到出现在块后面的let声明,但在let声明被执行前不能以任何形式来引用未声明的变量。这样的在let声明触发前的区域就被称之为占时性死区,在此阶段引用任何后面才声明的变量都会抛出ReferenceError。
4、全局声明
与var不同的是,使用let声明的全局变量不会成为window对象的属性:
var name = '苦哈哈';
let age = 18;
console.log(window.name); // 苦哈哈
console.log(window.age);//undefined
5、循环变量中的let
在let出现之前,for循环定义的迭代变量会渗透到外部:
for(var i = 0; i < 9 ; i++){
// 循环逻辑
}
console.log(i);// 输出:9
有了let之后这种问题就消失了,因为for的迭代变量的作用域仅限于循环体內部的块及作用域:
for(let i = 0; i < 9 ; i++){
// 循环逻辑
}
console.log(i); //ReferenceError: i 没有定义
在使用var进行for循环时最常见的问题就是迭代变量奇特的声明和修改:
//这个循环会输出5个5
for(var i = 0; i < 5; i++){
setTimeout(()=>{
console.log(i);
},0);
}
之所以会这样,是因为迭代变量保存的是导致退出循环的值,这使得在执行异步函数的时候所有的i都指向了那个导致循环退出的值。
然而在使用let声明迭代变量时,每一次循环都会声明一个新的变量,每一个setTimeout引用的都是不同的变量实例,所以console.log()才能输出我们期望的值,也就是每次执行循环使得迭代变量值:
for(var i = 0; i < 5; i++){
setTimeout(()=>{
console.log(i);
},0);
}
// 输出:0、1、2、3、4
const
const与let基本相同,唯一重要的区别就是const声明的变量必须同时初始化,且尝试修改const声明的变量会导致运行时报错:
const age = 26;
age = 36; // TypeError: 给常量赋值
const的问题: const本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const无法真正冻结对象,想要冻结对象就要使用Object.freeze()
// 创建foo常量 使用Object.freeze冻结
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
面对很复杂的对象时,如果想要彻底冻结它,可以遍历它的属性,判断它的属性类型,对它进行冻结。方法有很多,例如利用回调、简单的if判断等等...