ES6学习一
1. 变量声明
es6总共有6种方式声明变量,分别是var
、function
、 let
、const
、import
、class
。var
和function
命令与es5没有区别,今天主要记录 let
和const
命令(import
和class
命令还没仔细了解,之后再做补充)。
1.1 let命令
作用:声明一个变量,但所声明的变量旨在let命令所在的块级作用域内有效,没有变量提升。
// 只声明
let a
// 声明时赋值
let b = 1
// 声明后赋值
let c
c=2
console.log(a); // undefined
console.log(b); // 1
console.log(c); // 2
1.2 const命令
作用:声明一个常量,和let命令类似。
声明时赋值初始化
同let命令相比,常量一旦声明,它的值就不能变了,所以在声明时就要给这个常量赋值初始化。
// 只声明
const a // SyntaxError: Missing initializer in const declaration
console.log(a); // 不执行
对象、数组等引用类型的数据
因为这些数据的地址保存的是指向实际数据的指针,所以可以对这些数据的内容进行修改。
但是如果直接替换或重新赋值整个对象或数组,那么会改变它的实际地址,出现报错。
const aoo = {
a: 1}
aoo.a = 2
aoo.b = 1
console.log(aoo); // { a: 2, b: 1 }
aoo = {
c: 3 }
console.log(aoo); // 不执行
冻结对象
要让对象不能修改,可以使用Object.freze()
const aoo = Object.freze({
a: 1}) // TypeError: Object.freze is not a function
aoo.a = 2 // 以下不执行
aoo.b = 1
console.log(aoo);
1.3 块级作用域
es6新增了块级作用域,只要有{}
,{}
内就代表一个块级作用域,使用let
和const
声明的变量都是在当前块级作用域内使用。
无块级作用域导致内层变量泄露为全局变量
在es5中只有全局作用域和函数作用域(函数开辟的作用域),所以会出现内层变量泄露为全局变量(例:循环)
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
因为没有块级作用域,只用作循环的内层变量i
泄露为全局变量
let s = 'hello';
for (let i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // ReferenceError: i is not defined
块级作用域限制用作循环的内层变量i
只在所在作用域内,外层没有i
这个变量
不能重复声明变量
再同一个块级作用域内,不能重复声明变量,否则报错
let a
let a = 1 // SyntaxError: Identifier 'a' has already been declared
1.4 无变量提升和暂时性死区
变量提升 在es5中或者是使用var
命令时会出现变量可以在声明之前使用,只是值为undefined。
有变量提升导致内层变量覆盖外层变量
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
因为变量提升()里声明的tmp覆盖了外层tmp的值
let tmp = new Date();
function f() {
console.log(tmp);
if (false) {
let tmp = 'hello world';
}
}
f(); // 2021-08-18T18:20:32.471Z
没有变量提升,外层的tmp的值仍是获取到的时间
es6使用let
和const
命令则不允许变量提升,它们所声明的变量一定要在声明后使用(包括typeof),否则报错。
console.log(s); // ReferenceError: Cannot access 's' before initialization
let s = 'hello';
这个报错提示的就是暂时性死区。
暂时性死区
暂时性死区 是使用let和const命令时形成一个封闭的作用域,变量声明位置之前都是“死区”,限制变量只能在声明位置的后面使用。
注意typeof使用时,未声明的变量不会报错;已声明的变量在声明之前使用会报错
console.log(typeof a); // undefined
console.log(typeof b); // ReferenceError: Cannot access 'b' before initialization
let b = 'b'
2. 变量解构、赋值、设置别名
解构:es6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值。
本质上这种写法属于模式匹配,只要两边的模式相同,左边的变量就会被赋予对应的值
let [a, b] = [1, 2]
console.log(a); // 1
console.log(b); // 2
不完全解构
但es6并未要求左右两边的模式完全一致,因此等号左边的模式只匹配一部分等号右边的模式,会出现不完全解构,即能匹配的部分解构赋值成功;不匹配的部分解构失败,变量的值就等于undefined
let [a, [b,c],d] = ['1', '2', '3']
console.log(a); // 1
console.log(b); // 2
console.log(c); // undefined
console.log(d); // 3
?当等号右边的数组的值是Number类型时,等号两边模式不同会报错
let [a, [b,c],d] = [1, 2, 3] // TypeError: undefined is not a function
console.log(a); //以下不执行
console.log(b);
console.log(c);
console.log(d);
2.1 不同类型数据的解构赋值
对象的解构赋值
对象是按照key值来对应解构的,key可以随意交换位置
let {
b, a, c } = {
a: 1,b:2}
console.log(a); // 1
console.log(b); // 2
console.log(c); // undefined
数组的解构赋值
数组是按照顺序一一对应来解构的,不能随意交换位置。可以想象数组的下标就是对象的key值,顺序不同则key就不同,因此不能随意交换位置。
// let [a, b] = [1, 2]
// console.log(a); // 1
// console.log(b); // 2
let [b, a] = [1, 2]
console.log(a); // 2
console.log(b); // 1
不过数组也可以作为特殊对象按照对象的方式进行解构
let {
1: b, 0: a } = [1, 2]
console.log(a); // 1
console.log(b); // 2
字符串的解构赋值
字符串会被转化成一个类似数组的对象,以数组的方式进行解构;
let [a, [b, c], d, e, f] = 'hello'
console.log(a); // h
console.log(b); // e
console.log(c); // undefined
console.log(d); // l
console.log(e); // l
console.log(f); // o
同样也可以作为特殊对象,以对象的方式进行解构
let {
0:a, 1:b, 2:c, 3:d, 4:e } = 'hello'
console.log(a); // h
console.log(b); // e
console.log(c); // l
console.log(d); // l
console.log(e); // o
数值和布尔值的解构赋值
数值和布尔值会被转化为它的包装对象,以对象的方式进行解构
let {
toString: s} = 123;
s === Number.prototype.toString // true
let {
toString: s} = true;
s === Boolean.prototype.toString // true
undefined和null的解构赋值
解构赋值的规则是,只要右边不是数组或对象或类似数组对象的值,就先将其转化为对象。由于undefined和null无法转化为对象,所以它们解构赋值时会报错
// let { prop: x } = undefined; // TypeError: Cannot destructure property 'prop' of 'undefined' as it is undefined.
let {
prop: y } = null; // TypeError: Cannot destructure property 'prop' of 'null' as it is null.
函数参数的解构赋值
函数参数可以根据不同的数据类型按照上面的规则进行解构赋值
[{
name: 'a'},{
name: 'b'},{
name: 'c'}].map(({
name}) => name)
2.2 默认值
有些时候,我们在解构时希望有一个默认值,在解构失败或确实等于undefined时使用这个默认值。
因为es6内部使用严格相等运算符(===)判断一个位置是否有值,所以只有严格等于解构失败的undefined时,默认值才会生效。
let {
a='01', b='02', c='03' } = {
a: 1, b: 2}
console.log(a); // 1
console.log(b); // 2
console.log(c); // 03
null值
根据规则,null值代表的的这个位置有值,值就为null,默认值不会生效。
let {
a='01', b='02', c='03' } = {
a: 1, b: 2, c: null}
console.log(a); // 1
console.log(b); // 2
console.log(c); // null
默认值引用解构其他的变量
默认值可以引用解构其他的变量,但改变量必须已经声明
let [x = 0, y = x] = []
console.log(x); // 0
console.log(y); // 0
x在y声明前已经声明并赋值为0,所以y可以使用x作为默认值
let [x = y, y = 0] = [] // ReferenceError: Cannot access 'y' before initialization
console.log(x); // 以下不执行
console.log(y);
x在y声明前使用y做默认值,因为没有变量提升,所以报错
2.3 别名
如果变量名和属性名不一样,那就需要把变量名设为属性名的别名
let {
message: msg } = {
message:'123' }
console.log(msg); // 123
console.log(message); // ReferenceError: message is not defined
其实平时写的 let {foo} = {foo:123}
是 let {foo:foo} = {foo:123}
的简写
对象解构赋值的内部机制时先找到同名属性,然后再赋值给对应的变量,真正被赋值的是后者,而不是前者