let 和 const 命令
let 用来声明变量,只在let命令所在的代码块中有效。【代码块:使用花括号包住的内容称为代码块{}】
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined
b // 1
表明:let声明的变量只在它所在的代码块有效。
for循环的计数器,就很合适使用let命令。
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。
下面的代码如果使用var,最后输出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。
for循环设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
let 不存在变量提升
var命令会发生“变量提升”现象,变量可以在声明之前使用,值为undefined。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
let命令声明的bar,不会发生变量提升。在声明它之前,变量bar是不存在的。
暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError: tmp is not defined
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总结, 在代码块内,使用let命令声明变量之前,该变量都是不可用的 这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
因此,不能在函数内部重新声明参数。
function func(arg) {
let arg;
}
func() // 报错
function func(arg) {
{
let arg;
}
}
func() // 不报错
const 命令
1.基本规则
声明只读常量
声明后不可修改
一旦声明必须赋值,不赋值,会报错。
const的作用域只在声明的块级域内有效。
声明的常量不提升,存在暂时性死区,只能在声明的位置后面使用
const声明的常量,与let一样不可重复声明(不管使用var、let、还是const声明均不可)
2.本质
并不是值不可变动,而是变量指向的内存地址所保存的数据不得改动
对于简单类型的数据(数值、字符串、布尔值),值保存在变量所指向的内存地址,因此等同于变量。
对于复合类型的数据(对象与数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证指针是固定的,即总指向另一个固定的地址,并不能控制其地址中所存的数据。所以将对象声明为常量时需要小心。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
为什么出现块级作用域
内层变量可能覆盖外层变量
var a = new Date();
function fn(){
console.log(a);
if(false){
var a = “hello”;
}
}
fn(); // undefined
用来计数的循环变量泄露为全局变量
var s = "hello";
for (var i = 0; i < s.length; i++) {
console.log(s[i]); // h e l l o
}
console.log(i); // 5
变量i只用来控制循环,但是循环结束之后,i变量并没有消失,而是泄露成了全局变量
顶层对象的属性
顶层对象:
在浏览器环境指的是window对象,在Node指的是global对象。
ES5之中,顶层对象的属性与全局变量是等价的。
var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1
let b = 1;
window.b // undefined
变量的解构赋值
目录结构:
一、什么是解构
二、解构数组
1、变量声明并赋值的解构
2、变量先声明后赋值的解构
3、默认值
4、交换变量
5、解析一个从函数返回的数组
6、将剩余数组赋值给一个变量
7、for循环解构
三、解构对象
1、基本赋值
2、无声明赋值
3、给新的变量名赋值
4、默认值
5、给新的变量命名并提供默认值
6、对象传参解构
7、解构嵌套对象和数组
8、For of 迭代和解构
什么是解构:
解构是可以快速取得数组或对象当中的元素或属性,ES6中按一定模式从数组和对象中提取值,对变量进行赋值,这被称为解构。(无需使用arr[x]或者obj[key]等传统方式进行赋值)。
以前,为变量赋值,只能直接指定值。
var a = 1;
var b = 2;
ES6允许写成下面这样。
var [a, b] = [1, 2];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
下面是一些使用嵌套数组进行解构的例子。
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
如果解构不成功,变量的值就等于undefined。
let [foo] = [];
let [bar, foo] = [1];
以上两种情况都属于解构不成功,foo的值都会等于undefined。
二、解构数组
1、 变量声明并赋值时的解构
var foo = ["one", "two", "three"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
2、变量先声明后赋值时的解构
通过解构分离变量的声明,可以为一个变量赋值。
var a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
3、默认值
为了防止从数组中取出一个值为undefined的对象,可以在表达式左边的数组中为任意对象预设默认值。
var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
4、交换变量
在一个解构表达式中可以交换两个变量的值。
没有解构赋值的情况下,交换两个变量需要一个临时变量(或者用低级语言中的XOR-swap技巧)。
var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
5、解析一个从函数返回的数组
从一个函数返回一个数组是十分常见的情况。解构使得处理返回值为数组时更加方便。
在下面例子中,要让 [1, 2] 成为函数的 f() 的输出值,可以使用解构在一行内完成解析。
function f() {
return [1, 2];
}
var a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
6、将剩余数组赋值给一个变量
当解构一个数组时,可以使用剩余模式,将数组剩余部分赋值给一个变量。
var [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]
注意:如果剩余元素右侧有逗号,会抛出 SyntaxError,因为剩余元素必须是数组的最后一个元素。
var [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma
7、for循环解构
var arr = [[11, 12], [21, 22], [31, 32]];
for (let [a, b] of arr) {
console.log(a);
console.log(b);
}
//11
//12
//21
//22
//31
//32