新的声明方式

声明方式
var 声明语句声明一个变量,并可选地将其初始化为一个值。

语法:

1
var varname1 = [= value1][, varname2][= value2]...[, varnameN][=valueN];

varname
变量名。变量名可以定义为任何合法标识符。
valueN
变量的初始化值。默认值是 undefined。

描述:
变量声明,无论发生在何处,都在执行任何代码之前进行处理
用var声明的变量的作用域是它当前的执行上下文。
重新声明一个 JavaScript 变量,它将不会丢失其值。
将赋值给未声明变量的值在执行赋值时将其隐式地创建为全局变量(它将成为全局对象的属性)。

声明和未声明变量之间的差异是:

  1. 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function x(){
    y = 1; // 在严格模式(strict mode)下会抛出 ReferenceError 异常
    var z = 2;
    }

    x();

    console.log(y); // 打印 “1”
    console.log(z); // 抛出 ReferenceError: z 未在 x 外部声明
  2. 声明变量在任何代码执行前创建,而非声明变量只有在执行赋值操作的时候才会被创建

    1
    2
    3
    4
    console.log(y);
    y = 1;
    console.log(y);
    // 打印 Error: y is not defined
    1
    2
    3
    console.log(y); // undefined 变量的初始化值。默认值是 undefined。
    var y = 1;
    console.log(y); // 1
  3. 声明变量是它所在上下文环境的不可配置属性非声明变量是可配置的(如非声明变量可以被删除)

    1
    2
    3
    4
    5
    6
    7
    8
    var a = 1;
    b = 2;

    delete this.a; // 在严格模式(strict mode)下抛出TypeError,其他情况下执行失败并无任何提示。
    delete this.b;

    console.log(a, b); // 抛出ReferenceError。
    // 'b'属性已经被删除。

    由于这三个差异,未能声明变量将很可能导致意想不到的结果。因此,建议始终声明变量,无论它们是在函数还是全局作用域内。 而且,在 ECMAScript 5 严格模式下,分配给未声明的变量会引发错误。

变量提升
由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用,这个行为叫做“hoisting”。“hoisting”就像是把所有的变量声明移动到函数或者全局代码的开头位置

1
2
3
4
5
6
bla = 2;
var bla;

// 可以隐式地(implicitly)将以上代码理解为:
var bla;
bla = 2;

因此,建议始终在作用域顶部声明变量(全局代码的顶部和函数代码的顶部),这可以清楚知道哪些变量是函数作用域(本地),哪些变量在作用域链上解决。
重要的是,提升将影响变量声明,而不会影响其值的初始化。当到达赋值语句时,该值将确实被分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
function do_something() {
console.log(bar); // undefined
var bar = 111;
console.log(bar); // 111
}

// is implicitly understood as:
function do_something() {
var bar;
console.log(bar); // undefined
bar = 111;
console.log(bar); // 111
}

声明并初始化两个变量:

1
var a = 0, b = 0;

给两个变量赋值成字符串值:

1
2
3
4
5
6
7
8
9
var a = "A";
var b = a;

// 等效于:
var a, b = a = "A";

// 留意顺序
var x = y, y = 'A';
console.log(x + y); // undefinedA

多个变量的初始化

1
2
3
4
5
6
7
8
9
10
var x = 0;

function f(){
var x = y = 1; // x在函数内部声明,y不是!
}
f();

console.log(x, y); // 0, 1
// x 是全局变量。
// y 是隐式声明的全局变量。

隐式全局变量和外部函数作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var x = 0;  // x是全局变量,并且赋值为0。

console.log(typeof z); // undefined,因为z还不存在。

function a() { // 当a被调用时,
var y = 2; // y被声明成函数a作用域的变量,然后赋值成2。

console.log(x, y); // 0 2

function b() { // 当b被调用时,
x = 3; // 全局变量x被赋值为3,不生成全局变量。
y = 4; // 已存在的外部函数的y变量被赋值为4,不生成新的全局变量。
z = 5; // 创建新的全局变量z,并且给z赋值为5。
} // (在严格模式下(strict mode)抛出ReferenceError)

b(); // 调用b时创建了全局变量z。
console.log(x, y, z); // 3 4 5
}

a(); // 调用a时同时调用了b。
console.log(x, z); // 3 5
console.log(typeof y); // undefined,因为y是a函数的本地(local)变量。

let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。

语法:

1
let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];

var1, var2, …, varN
变量名。必须是合法的标识符。
value1, value2, …, valueN
变量的初始值。可以是任意合法的表达式。

描述:
let 允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与 var 关键字不同的是,var 声明的变量只能是全局或者整个函数块的。 var 和 let 的不同之处在于后者是在编译时才初始化

就像const 一样,let不会在全局声明时(在最顶部的范围)创建window 对象的属性

作用域规则
let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function varTest() {
var x = 1;
{
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}

function letTest() {
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}

函数或代码顶部let 不会在全局对象里新建一个属性。var声明会给全局对象新增属性

1
2
3
4
var x = 'global';
let y = 'global';
console.log(this.x); // 'global'
console.log(this.y); // 'undefined'

模仿私有成员
在处理构造函数的时候,可以通过 let 声明而不是闭包来创建一个或多个私有成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var Thing;

{
let privateScope = new WeakMap();
let counter = 0;

Thing = function() {
this.someProperty = 'foo';

privateScope.set(this, {
hidden: ++counter,
});
};

Thing.prototype.showPublic = function() {
return this.someProperty;
};

Thing.prototype.showPrivate = function() {
return privateScope.get(this).hidden;
};
}

console.log(typeof privateScope);
// "undefined"

var thing = new Thing();

console.log(thing);
// Thing {someProperty: "foo"}

thing.showPublic();
// "foo"

thing.showPrivate();
// 1

可以使用var创建和闭包具有相同隐私模式的局部变量,但是它们需要函数作用域(通常是模块模式中的IIFE),而不仅仅是上面示例中的块作用域。

重复声明
同一个函数或块作用域中重复声明同一个变量会引起SyntaxError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (x) {
let foo;
let foo; // SyntaxError thrown.
}

let x = 1;
switch(x) {
case 0:
let foo;
break;

case 1:
let foo; // SyntaxError for redeclaration.
break;
}

然而,需要特别指出的是,一个嵌套在 case 子句中的块会创建一个新的块作用域的词法环境,就不会产生上诉重复声明的错误。

1
2
3
4
5
6
7
8
9
10
11
let x = 1;
switch(x) {
case 0: {
let foo;
break;
}
case 1: {
let foo;
break;
}
}

暂存死区
与通过 var 声明的有初始化值 undefined 的变量不同,通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。

1
2
3
4
5
6
function do_something() {
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2;
}

暂存死区与 typeof
与通过var声明的变量, 有初始化值 undefined和只是未声明的变量不同的是,如果使用typeof检测在暂存死区中的变量, 会抛出ReferenceError异常:

1
2
3
4
5
6
// prints out 'undefined'
console.log(typeof undeclaredVariable);

// results in a 'ReferenceError'
console.log(typeof i);
let i = 10;

const
常量是块级作用域,很像使用 let 语句定义的变量。常量的值不能通过重新赋值来改变,并且不能重新声明

语法:
const name1 = value1 [, name2 = value2 [, … [, nameN = valueN]]];
nameN
常量名称,可以是任意合法的标识符。
valueN
常量值,可以是任意合法的表达式。

描述:
此声明创建一个常量,其作用域可以是全局或本地声明的块。 与var变量不同,全局常量不会变为窗口对象的属性。需要一个常数的初始化器;也就是说,您必须在声明的同一语句中指定它的值(这是有道理的,因为以后不能更改)。
const 声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容(例如,其参数)。
一个常量不能和它所在作用域内的其他变量或函数拥有相同的名称。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 注意: 常量在声明的时候可以使用大小写,但通常情况下全部用大写字母。 

// 定义常量MY_FAV并赋值7
const MY_FAV = 7;

// 报错
MY_FAV = 20;

// 输出 7
console.log("my favorite number is: " + MY_FAV);

// 尝试重新声明会报错
const MY_FAV = 20;

// MY_FAV 保留给上面的常量,这个操作会失败
var MY_FAV = 20;

// 也会报错
let MY_FAV = 20;


// 注意块范围的性质很重要
if (MY_FAV === 7) {
// 没问题,并且创建了一个块作用域变量 MY_FAV
// (works equally well with let to declare a block scoped non const variable)
let MY_FAV = 20;

// MY_FAV 现在为 20
console.log('my favorite number is ' + MY_FAV);

// 这被提升到全局上下文并引发错误
var MY_FAV = 20;
}


// MY_FAV 依旧为7
console.log("my favorite number is " + MY_FAV);

// 常量要求一个初始值
const FOO; // SyntaxError: missing = in const declaration

// 常量可以定义成对象
const MY_OBJECT = {"key": "value"};

// 重写对象和上面一样会失败
MY_OBJECT = {"OTHER_KEY": "value"};

// 对象属性并不在保护的范围内,下面这个声明会成功执行
MY_OBJECT.key = "otherValue";

// 也可以用来定义数组
const MY_ARRAY = [];
// It's possible to push items into the array
// 可以向数组填充数据
MY_ARRAY.push('A'); // ["A"]
// 但是,将一个新数组赋给变量会引发错误
MY_ARRAY = ['B']

总结归纳

使用var声明建议
建议始终在全部代码的顶部和函数代码的顶部声明变量.

使用var需要注意:
声明提前/变量提升。(变量声明在任意代码执行之前处理)
未声明的变量是全局变量,未声明的变量赋值才会被创建。
全局声明时,创建 window 对象的属性

1
2
var a;
console.log(typeof a ,typeof b); // undefined undefined

使用let需要注意:
同一个函数或块作用域不能重复声明。
暂存死区(通过 let 声明的变量直到它们的定义被执行时才初始化)。
不会创建window 对象的属性

1
2
3
console.log(typeof undeclaredVariable); // undefined
console.log(typeof i); // undefined
let i = 10;

使用const建议:
建议变量名全部用大写字母。例如MY_FAV

使用const需要注意:
同一个函数或块作用域不能重复声明。
不能重新赋值。
声明同时指定值,值只读引用,值是对象时可改变。

以上是我对下列视频及文章的归纳和总结。
ES6 免费视频教程

参考资料:
MDN var
MDN let
MDN var
ECMAScript 6 入门-let和const 命令
ES6的开发环境搭建

相关代码仓库:
ES6

作者

Fallen-down

发布于

2020-01-28

更新于

2020-03-09

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.