# 新增let const命令

# let命令

类似var,但是所申明的变量只在let命令所在的代码块内有效。

for(let i = 0;i<10;i++) {
    
}
console.log(i) // Uncaught ReferenceError: i is not defined
1
2
3
4
  1. 不存在变量提升

var会存在变量提升,即变量可以在声明前使用,值为undefined。函数表达式和箭头函数不会发生函数提升

  1. 暂时性死区

只要块级作用域内存在let命令,所声明的变量就绑定这个区域,不受外部影响。

var me = 'xiuyan'
{
    me = 'bear'
    let me;
}
// Uncaught ReferenceError: Cannot access 'me' beforeinitialization 
1
2
3
4
5
6

如果区块中存在let/const命令,这个区块对这些声明的变量,从一开始就形成封闭作用域。假如我们在声明前去使用这类变量,就会报错。这就是暂时性死区。起始于函数开头,终止于相关变量声明语句的所在行

function bar1() {
    console.log(foo3) // 暂时性死区
    let foo3 = 'foo3'
    console.log(foo3) // 这里可以正常访问,当然是在前面不报错的前提下。
}
1
2
3
4
5

暂时性死区,函数的参数默认设置也会受它影响

function foo(arg1=arg2,arg2) {
    console.log(`${arg1} ${arg2}`)
}
foo(undefined,'arg2')
// 因为arg2在后面还未定义,所以报错:Uncaught:ReferenceError: arg2 is not defined
1
2
3
4
5
  1. 不允许重复声明
function foo(arg1) {
    let arg1
}
foo('arg1')
1
2
3
4

报错:Uncaught SyntaxError:Identifier 'arg1' has already been declared.

# const命令

const保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据,值就保存在变量指向的内存地址中,因此等同于变量。但对于引用类型数据来说,变量指向的内存地址保存的只是一个指针。因此声明一个对象为常量必须非常小心

const foo = {}
// 可以
foo.prop = 123;

//将foo指向另一个对象,报错
foo = {} // TypeError: "foo" is read-only
1
2
3
4
5
6

# 新的声明变量方式差异

  1. 顶层对象的属性
var a = 2
console.log(window.a === 2) // true

let b = 3;
console.log(window.b) // undefined

const c = 4
console.log(window.c) // undefined
1
2
3
4
5
6
7
8

因为const/let会生成块级作用域,可以理解为

let a = 10;
const b = 20;
相当于:
(function(){
    var a = 10;
    var b = 20;
})()
1
2
3
4
5
6
7
  1. var存在变量提升,let/const没有。
  2. 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部。

# 函数/变量提升

console.log(foo); 
var foo = 1  //变量提升
console.log(foo)
foo()
function foo(){ //函数提升
   console.log('函数')
}

// 等价
function foo(){ //提到顶端
   console.log('函数')
}
var foo  
console.log(foo) //输出foo这个函数,因为上面foo没有被赋值,foo还是原来的值 
foo = 1;  //赋值不会提升,赋值后 foo就不再是函数类型了,而是number类型
console.log(foo) //输出1
foo() //这里会报错,因为foo不是函数了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var temp = 1; 
function test() { 
    temp = 10; 
    return; 
    function temp() {} 
} 
test(); 
console.log(temp); // 1

// 预编译阶段,函数提升
var temp = 1; 
function test() { 
    function temp() {} 
    temp = 10; 
    return; 
    // 因此导致这里面相当于创建了一个值为10的局部变量temp
} 
test(); 
console.log(temp);// 打印的还是全局的了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log(a)
var a = 1
console.log(a)
function a(){console.log(2)}
console.log(a)
var a = 3;
console.log(a)
function a() {
    console.log(3)
}
a()
console.log(a) 

// 等价于
function a() {console.log(3)} // 函数提升了两个,后面覆盖了前面console.log(2)这个
var a
console.log(a) // function a() {console.log(3)} 因为var a并没有赋值,所以打印出函数
a = 1
console.log(a) // 1
console.log(a) // 1
a = 3
console.log(a) // 3
a() // 报错了,此时a不是函数,没办法调用
console.log(a)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# var,let,const区别的实现原理

TIP

  • var的话会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针
  • let的话,是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错
  • const的话,也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性

# Babel编译处理

TIP

  • Babel编译会将const/let编译为var。为了保证const不可变性,Babel如果在编译过程中发现对const声明的变量进行二次赋值,则会直接报错,这样就可以在编译阶段对错误进行处理。
"use strict"
function _readOnlyError(name) {
    throw new Error("\" + name + "\" is read-only")
}
var foo = 0;
foo = (_readOnlyError("a"),1)
1
2
3
4
5
6

Babel只要检测到const声明的变量被改变赋值,就会主动插入一个_readOnyError函数,并执行此函数。这个函数的执行内容就是报错,因此代码执行时就会直接抛出异常。

  • 至于let的块级概念,在ES5中一般通过立即调用函数表达式实现块级作用域,但是Babel对此的处理非常取巧,会在块内给变量换一个名字,这样在块外自然就无法被访问到了。

  • 暂时性死区又是如何被Babel编译的呢?。其实Babel在编译时会将let,const变量重新命名,同时在js严格模式下不允许使用未声明的变量,这样在声明前使用这个变量就会报错。

  • 对于经典的for循环问题。Babel处理并不让感到意外,具体还是使用闭包来存储变量

let array = []
for(let i=0;i<10;i++) {
    array[i] = function() {
        console.log(i)
    }
}
array[6]() // 6

let array = []
for(var i=0;i<10;i++) {
    array[i] = function() {
        console.log(i)
    }
}
array[6]() // 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Babel还使用了闭包保存每个循环变量i的值。

'use strict'
var array = []
var _loop = function _loop(i) {
    array[i] = function() {
        console.log(i)
    }
}
for(var i=0;i<10;i++) {
    _loop(i)
}
array[6]()
1
2
3
4
5
6
7
8
9
10
11