ES6 之前,我们一直使用 var 定义变量, 而且可以反反复复的修改var定义的内容,比如从基础类型改为引用类型,引用类型改为基础类型
例如:
示例-1
// 基本类型
var a = 1
// 直接修改为引用类型
a = {
name: "张三"
}
console.log(a) // {name: "张三"}
示例1,直接将基本类型修改为引用类型, 也不会报错,也不会有任何 warning
示例-2
// 从写法上,意图是定义一个常量
var pi = 3.14
// 修改为一个 函数
pi = function() {
return true
}
pi() // true
示例2,直接将 我们认知的一个常量,修改为 一个function , 也是不会报错的
所以, 我们知道 javascript在灵活方面,确实比强类型语言灵活,但灵活的同时, 缺乏约束, 在大型协同的过程中,导致各种变量值污染, 及其难排查
所以, ES6 推出let 和const 在语法层面上, 对代码进行强约束,具体的区别如下
一、作用域不同
在ES5的时候,js只有函数级作用域和全局作用域,没有块级作用域,所以,会看到以下的情况
for(var i=0; i<5; i++) {
// do something
}
console.log(i) // 5
为什么是 5呢,其实 for这个括号里的var i 和下面的是等价的
var i; // undefined
for(i=0; i<5; i++) {
}
console.log(i)
因为var 定义的 根本不是块级作用域, i通过变量提升,提升到了最外层作用域, 所以,var定义的 i 跟外面的作用域 是一样的, 所以可以访问
let 和 const
let和const 都是块级作用域, 和面向对象的 java 等强类型语言,是一致的
- 未定义的时候, 不能提前访问,否则报 Cannot access 'a' before initialization
console.log(a)
let a = 1
意思很明确,不能在定义之前去访问 a变量
- 有了块级作用域
{
let a = 10;
var b = 7;
}
console.log(b) // 7
console.log(a) // Uncaught ReferenceError: a is not defined
这里就对比了 let 和 var在作用域的区别, b没有块级作用域的概念,所以,是和 下面的console是同级的, 而let 定义的就被限定在 块级作用域内, 外部无法访问,所以报错
for (let x = 0; x < 5; x++) {
}
console.log(x) // x is not defined
这个常见的 for循环 也是 let 和 var的一个区别,其实质就是 2个 (var 没有块级作用域的概念, let有; var会存在变量提升,let不会)
示例
var arr = [];
for(var i=0; i<5; i++) {
arr[i] = function() {
console.log(i)
}
}
arr[3]() // 5
arr[4]() // 5
上面的i 仍旧是全局的, 所以,每次在循环迭代的时候,是将全局的 i 更新了 然后再执行方法的时候, 由于方法内部没有找到i的定义,就往上层找, 然后找到了全局, 而此时全局的 i 已经是 5, 所以 执行 arr[3]() 和 arr[4]() 都是 5
二、var 定义的变量,会挂载到window对象上, let和const不会
我们定义一个 var 变量, 会将值默认挂在到 window对象上, 当然,会有2种情况会自动挂载
- 全局定义的变量
var a = 1;
// 等价于
window.a = a
- 函数内使用未定义的变量, 等价于在全局声明 window.XXX属性
function f() {
b = 12
}
// 执行 f, 下面的 b就是 12
// f()
console.log(window.b)
其实,js是存在变量提升的问题,导致 上面的 window.b js在未执行的时候,会先分析,哪些变量,需要进行提升, 然后如果在当前的作用域中没有找到,就继续往上层找(追溯), 直到到 全局对象上为止(浏览器的顶层对象是 window, nodejs中是 global)
当发现 b 有被使用,但是在 f函数这个作用域中没有找到定义的b的语句, 就继续找,往上一层,就找到了全局 window
所以,就会先静默定义一个 window.b = undefined
三、变量提升
let 和 const 不存在变量提升
var存在变量提升
let 和 const区别
共同点:
- 都有块级作用域
- let和const都不存在变量提升,一定是先定义,后使用
- 都有暂时性死区(TDZ) 在没有定义之前,不能使用
- 都不允许重复声明 let a = 1; let a = 2;
不同点:
- const 只是常量, 赋予const 类型是基本类型,就不能再改变值
- const 对于基本类型来说, 只能赋值一次,所以,第一次初始化,就要赋予一个值; 引用类型,就可以反复修改内容
const a = 1;
const a = 2; // 报错 Identifier 'a' has already been declared
告诉你 a 已经被定义
const a;
a = 1 // Missing initializer in const declaration
没有初始化
const o = {}
o.name = '张三'
o.age = 12
console.log(o)
这种是可以的, 因为 o 是一个对象, 是引用类型,变量名 o 不直接指向数据, 而是指向数据所在的一块 内存地址; 只要我们不要将 指向的地址修改掉, 就可以任意修改这个地址所指向的值
const o = {}
// 这里就是将引用地址修改了,就会报错
o = {} // Assignment to constant variable.对常量变量赋值。