前言: 本内容主要来自于某平台的视频课程,另外还包含一些个人的理解以及在学习的过程中遇到的问题和解决方法。希望自己可以对这部分知识加深理解,加强记忆。也希望可以帮到正在学习这部分内容的童鞋。
目录
一、解构赋值概念
解构赋值:解析某数据类型值得结构,将我们想要的东西提取出来,赋值给变量或常量。
它的本质是赋值运算,赋值运算语法如下所示:
变量 = 表达式;
功能是把表达式的值,赋予变量。(底层就是把值放入指定的内存,变量名相当于内存地址)
表达式的形式:
-
变量,例如,变量a (let a = 1)
-
字面量,例如,1、“Hello”、true
-
能够计算出结果都变量、字面量和运算符的组合,例如,1 + 1; 2 + a (变量a的值为1)(该形式的表达式,下文简称组合)
在C语言中,有左值和右值的概念。简单来说,左值表示一个地址,因此必须是变量或常量。右值,它是一个表达式,表达式(变量、字面量、组合)最终可以表示为一个值。
在赋值运算符中,位于赋值运算符左边的变量是左值表示一个地址,位于赋值运算符右边的变量(属于表达式的形式之一)表示一个值。以如下代码为例,a是左值表示地址,b是右值表示数字1。
let a, b = 1;
a = b;
字面量,组合自不必说,表示一个值,只能作为右值。变量依据所处的位置决定是左值还是右值。
二、不同数据类型的解构赋值
1·、数组
(1) 解构赋值原理
- 结构(模式)匹配
- 索引值相同的完成赋值
语法规则
变量声明关键字:var let const(声明常量)
// 变量声明时初始化,适用于变量或者常量
变量声明关键字 [变量1, 变量2, …] = [元素1, 元素2, …] ;
// 变量先声明后初始化,只适用于变量,常量声明时必须初始化
变量声明关键字 变量1, 变量2, …;
[变量1, 变量2, …] = [元素1, 元素2, …];
看起来并没有什么特别之处,类似的代码,也许你早就写过,如下代码所示。只不过解构赋值,需要先解析提取数组对象的元素值,然后在批量赋值罢了。
// 声明时初始化
let num = 1;
let num1 = 1, num2 = 2, ...; // 处理多个变量的赋值
// 先声明后初始化
let num;
num = 1;
let num1, num2, ...; // 处理多个变量的赋值
num1 = 1, num2 = 2,...;
我们暂时使用let关键字,数组元素使用number类型,来说明下数组结构赋值的原理。
浏览器控制台运行结果,如下图所示:
(2) 忽略数组中的某个值
如果不需要取数组中的某个值,只要在索引值相同的位置不放置变量接收对应的值即可。
这里为了提高代码可读性,我放了一个空格,其实可以什么都不放[a,,c]
。
(3) 取不到数组中的某个值
变量的个数超过了数组中值的个数,就会有变量取不到值,那么它的值就是undefined
或者
(4) 默认值
基本用法
语法规则
变量声明关键字:var let const(声明常量)
// 变量声明时初始化
变量声明关键字 [变量1 = 默认值, 变量2 = 默认值, …] = [元素1, 元素2, …] ;
// 变量先声明后初始化,只适用于变量,常量声明时必须初始化
变量声明关键字 变量1, 变量2, …;
[变量1 = 默认值, 变量2 = 默认值, …] = [元素1, 元素2, …];
过程就是需要把数组解构提取出值,然后赋予变量。如果提取不到值(越界访问,或者数组中存储的值本身就是undefined),就把默认值赋予变量。
生效条件
当且仅当数组元素的值严格等于(运算符===)undefined时,默认值才会生效。
如图所示,null === undefined 并不成立,因此默认值未生效b的值为null。
数组中某个索引位置未放入值,或者显示地放入的值为undefined,那么默认值生效,c和d的值为 -1。
惰性求值
在文章的开始,我们知道赋值运算符的右边是表达式。那么,默认值也可以是表达式。如下代码所示
let [a = 1 + 1] = [1]
惰性求值是指表达式1 + 1并不会马上执行,只有当a的值在数组中取不到时,才开始执行表达式的运算。很容易理解,因为表达式还可能是复杂的函数,浪费很多时间,先计算出来了,结果数组可以提取到对应的值,计算出的值就用不到了,时间白白浪费了。如下代码所示
function foo () {
// 耗时代码
}
let [a = foo()] = [1];
(5) 应用
类数组
Arguments、NodeList等类数组的解构赋值。
函数参数(较常用)
交换两个变量的值
赋值运算符的左边[x, y]是左值,变量x, y先声明,后初始化。赋值运算符的右边[y, x]是右值,表示一个值,等价于[‘Y’, ‘X’]。
2、对象
(1) 解构赋值原理
- 结构(模式)匹配
- 属性名相同的完成赋值
语法规则
变量声明关键字:var let const(声明常量)
// 变量声明时初始化,变量、常量都可
变量声明关键字 {属性1: 变量1, 属性2: 变量2, …} = {属性1: 属性值, 属性2: 属性值, …} ;
// 变量先声明后初始化,只适用于变量,常量必须声明时初始化
变量声明关键字 变量1, 变量2, …;
{属性1: 变量1, 属性2: 变量2, …} = {属性1: 属性值, 属性2: 属性值, …} ;
我们暂时使用let关键字, 对象使用string类型name属性和number类型大age属性,来说明下数组结构赋值的原理。
本质上,和数组没有区别。数组的索引可以看成(本来也是)属性,只不过属性名是数字字符串而已。
当接收值的变量与属性名同名,属性名可以省略,只写变量名,简写形式如下:
测试代码及运行结果
(2) 变量先声明后初始化
当我们像下图这样书写代码,{name, age} 被JavaScript引擎认为是代码块(代码块就是包含多条简单语句的集合,例如if语句,while语句,一般会有代码块)
在代码块后跟一个赋值运算符,显然会报错。
当我们像下图这样书写时,{name: name, age: age} 也会被当成一个代码块
name: 被当成时循环中的标签,标签可以设置break,continue跳出循环后开始运行代码的位置。
name, age 认为是逗号运算符组成的表达式,该表达式会以第二个操作数的结果作为整体表达式的值。
然后有出现一个冒号 : ,JS引擎无法解释它的用途,因为不符合语法。
逗号运算符组成的表达式案例,运行结果如下图所示
解决方案
使用小括号包裹整个解构赋值语句,大括号不再被JavaScript引擎解析为代码块。
此方法不仅用于解构赋值,在任何可能引起歧义的地方——你书写的是对象,但是引擎却解析为代码块,都可以使用小括号包裹。代码及运行结果,如下图所示。(简单说明,这是箭头函数的使用规则,当代码块中只有一条表达式语句,可以直接书写,不用return。箭头函数也挺复杂的,不过不是本内容的重点。想要了解箭头函数的用法,请转到👉ES6箭头函数用法)
数组中是不存在这样的问题的,
(3) 可以取到继承的属性
toString方法(属性值为函数类型的属性称方法)在Object的prototype中定义
(4) 默认值
基本用法
变量声明关键字:var let const(声明常量)
// 变量声明时初始化,变量、常量都可
变量声明关键字 {属性1: 变量1 = 默认值, 属性2: 变量2 = 默认值, …} = {属性1: 属性值, 属性2: 属性值, …} ;
// 变量先声明后初始化,只适用于变量,常量必须声明时初始化
变量声明关键字 变量1, 变量2, …;
{属性1: 变量1 = 默认值, 属性2: 变量2 = 默认值, …} = {属性1: 属性值, 属性2: 属性值, …} ;
// 变量名和属性名相同
变量声明关键字 {变量名 = 默认值, …} = {属性1: 属性值, …}
测试代码及运行结果
生效条件和惰性求值同上,不再赘述。
(5) 应用
函数的参数
3、字符串
前文介绍过,数组的解构赋值和对象的解构赋值本质相同,都是属性名相同完成赋值,只不过数组的属性名是数字(字符串), 而对象的属性名通常是非数字字符串。
字符串对象的属性,如果下图所示
属性既有字符串又有数字(字符串),解构赋值的左值可以使用数组结构或者对象结构。(仅针对字符串,其他类型不一定可以,对象类型{'0': 1, 'b': 2}
属性为数字字符串,依然不可以使用数组解构,可以使用数组解构的必要条件是该类型是可迭代的,下文介绍)
基本类型会“自动装箱”(Java中的术语,从基本类型转换为对应的引用类型,即包装类)
4、数字
如下图所示,数字对象并没有属性,因此解构数字对象,没有太大意义。[[ ]] 包裹名称,不是属性,它们专门给JavaScript引擎使用的。
但是,可以获得Number的prototype对象中定义的属性,本质是获取继承的属性。
4、布尔
同数字,不在做过多介绍。
5、null
null为基本类型且没有包装类,不能进行解构赋值。
错误:不能够获取null的属性。因为null不是对象,就没有属性。
6、undefined
undefined为基本类型且没有包装类,不能进行结构赋值。
可以重点记忆一下报错信息,因为函数的参数应用解构赋值(较常见),而你忘记了传递参数,引擎会传递undefined作为参数的值,然后解构undefined,就会遇到这个错误。如下图所示,之前使用过的函数
三、其他注意事项
用对象解构数组
数组也是对象,因此左值可以是对象结构。虽然没有太大意义。
但是再次验证该结论:数组的解构赋值和对象的解构赋值本质相同,都是属性名相同完成赋值,只不过数组的属性名是数字(字符串), 而对象的属性名通常是非数字字符串
数组只能解构实现了可迭代协议的类型的对象(重要)
左值使用数组结构结构其他类型值
报错: Uncaught TypeError: XXXXX is not iterable,它只能解构实现了可迭代协议的对象,例如Array、String、Map等等。
这个错误也是建议记住的,因为函数的参数应用了解构赋值,而你有忘记了传递参数(较常见),引擎会传递undefined作为参数的值,然后解构undefined,就会遇到这个错误。如下图所示,之前使用过的函数
剩余参数和解构赋值结合
想要了解详细信息,请参考:ES6剩余参数的使用
四、总结
- 解构赋值左值只有数组和对象两种结构。数组、对象的解构赋值原理:结构匹配;属性名(数组中称为索引)相同完成赋值。
- 默认值生效条件,当且仅当右值对象或数组的属性值严格等于undefind;默认值惰性求值。
- 对象解构赋值,左值中变量已提前声明,为避免JavaScript引擎把大括号解析为代码块,需要还用小括号包裹解构赋值表达式。
- 对象的解构赋值可以取到继承的属性和方法。
- 基本类型字符串、数字、布尔解构赋值时,右值会自动装箱(转换为对应的包装类)
- 字符串左值可以使用数组结构或者对象结构进行解构赋值。数字、布尔对象没有属性,解构赋值意义不大,但是能获取继承的属性和方法。
- null和undefind是基本类型,没有包装类,无法转换为对象,因此无法结构赋值
- 由于解构赋值常用于函数的参数,因此要牢记这两个报错:
- Uncaught TypeError: undefined is not iterable.
- Uncaught TypeError: Cannot red properties of undefined.