JavaScript之变量的解构赋值全面解析(●'◡'●)

目录

  1. 数组的解构赋值
  2. 对象的解构赋值
  3. 字符串的解构赋值
  4. 数值和布尔值的解构赋值
  5. 函数参数的解构赋值
  6. 圆括号问题
  7. 参考 | 推荐

数组的解构赋值

基本用法

ES6 允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

js 复制代码
let [a, b, c] = [1, 2, 3];

上面的代码表示,可以从数组中提取值,按照对应的位置,对变量赋值。

本质上,这种写法属于"模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值。例如:

js 复制代码
let [foo1, [foo2, [foo3]] = [1, [2, [3]];
foo1 // 1
foo2 // 2
foo3 // 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

js 复制代码
let [foo1] = [];
foo1 // undefined
let [bar, foo2] = [1];
foo2 // undefined

可以看出,上面两个都属于解构不成功,foo1,foo2 都为undefined

还有一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。在这种情况下,解构依然可以成功。

js 复制代码
let [x, y] = [1, 2, 3];
x // 1
y // 2

可以看到等号右边的3并没有与之配对的变量,但解构依然成功,这就是不完全解构。

如果等号右边不是数组(或者严格地说,不是可遍历的解构),那么将会报错

js 复制代码
let [foo1] = 1;
let [foo2] = false;
let [foo3] = NaN;
let [foo4] = undefined;
let [foo5] = null;
let [foo6] = {};

上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式), 要么本身就不具备Iterator接口。

其实,只要某种数据结构具有Iterator 接口, 都可以采用数组形式的解构赋值。

js 复制代码
let [x, y, z] = new Set(['a', 'b', 'c']);
x // 'a'
js 复制代码
function* fibs() {
    let a = 0;
    let b = 1;
    while(true) {
        yield a;
        [a, b] = [b, a + b];
    }
 }
 let [first, second, third, fourth, fifth, sixth] = fibs();
 sixth // 5

fibs是一个Generator函数,原生具有Iterator接口,解构赋值会依次从这个接口获取值。

默认值

解构赋值允许指定默认值

js 复制代码
let [foo1 = 1] = [];
let [foo2 = 2] = ["two"];
let [foo3 = 3] = [undefined]
let [foo4 = 4] = [null]
foo1 // 1
foo2 // two
foo3 // 3
foo4 // null

ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

默认值也可以是表达式,这个表达式是惰性求值的,即只有在用到的时候,才会求值。

js 复制代码
function f() {
    console.log('hello');
}
let [x = f()] = [];
x // 'hello'

默认值也可以引用解构赋值中的其他变量,但该变量必须要先声明

js 复制代码
let [foo1 = 1, bar1 = foo1] = [];    // foo1=1;bar1=1
let [foo2 = 1, bar2 = foo2] = [2];   // foo2=2,bar2=2
let [foo3 = 1, bar3 = foo3] = [1,2]; // foo3=1,bar3=2
let [foo4 = bar4, bar4 = 1] = [];    // ReferenceError : bar4 is not defined

最后一个表达式之所以会报错,是因为在给foo4赋值之前,bar4还没有声明。

对象的解构赋值

基本用法

变量的解构是用{}包括的,而数组是用[]解构

js 复制代码
let { foo, bar } = {foo : 'aaa', bar: 'bbb'}
foo // 'aaa'
bar // 'bbb'

对象的解构和数组还有一个明显的不同,那就是数组的元素是按次序排列,变量的取值由它的位置决定;而对象的解构没有次序,变量必须与属性同名,才能取到正确的值。

js 复制代码
let {bar, foo} = {foo : 'aaa', bar: 'bbb'}
foo //'aaa'
bar // 'bbb'

let {baz} = {foo : 'aaa'}
baz // undefined

可以发现,第一个例子中,即使barfoo的次序与等号右边的不一致,也能取到同名对应的值;而第二个例子中,即使bazfoo的次序对应,但因为等号右边没有与baz同名的属性,所以为undefined

对象解构失败,其变量的值也等于undefined

js 复制代码
let {foo} = { bar : 'aaa' }
foo // undefined

对象解构赋值, 可以很方便的把现有对象的方法赋值给一个变量。

js 复制代码
let {log, sin, cos} = Math;

const {log} = console;
log{'hello'} // hello

上面的第一个代码将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来更方便,例二也是如此。将console对象中的log方法赋值给log对象。

如果变量名与属性名想要不一致,那就必须写成下面这样。

js 复制代码
let {foo : a} = {foo : '1', bar: '2'};
a // '1'

其实对象的解构赋值就是下面形式的简写。

js 复制代码
let { foo : foo, bar : bar } = { foo : 'aaa', bar : 'bbb'}

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是冒号后面的,前面的只是用来找到属性的key。

js 复制代码
let { foo : baz } = { foo : 'aaa', baz : 'bbb' }
foo // foo is not found
baz // 'aaa'

与数组一样,解构也可以用于嵌套解构的对象。

js 复制代码
let obj = {
    p : [
        'hello',
        {y : 'world'}
    ]
}
let { p : [x, { y }] } = obj;
//x 'hello'
//y 'world'

注意, 这时p是模式,不是变量,p只是用来匹配的key,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。

js 复制代码
let obj = {
    p : [
        'hello',
        {y : 'world'}
    ]
}
let { p, p : [x, { y }] } = obj;
// p ['hello', {'world'}]
// x 'hello'
// y 'world'

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错

js 复制代码
// 报错
let { foo : {bar}} = { bar: 'bar' }

因为只有子属性bar,没有父属性foo。在解构时,fooundefined,再取foo的子属性就会报错。

对象的解构赋值也可以取到继承的属性。

js 复制代码
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf( obj1, obj2 );

const { foo } = obj1;
foo // 'bar'

obj1的原型对象为obj2,而obj2有一个foo属性,在解构时,foo变量会顺着原型链依次查找是否有同名的属性,若有则赋值给foo

默认值

对象解构也可以指定默认值

js 复制代码
var { x = 3 } = {};
x // 3

var { x : y = 2 } = {} 
y // 2

var { x : y = 2 } = {x = 1};
y // 1

var { x : y = 'hello'} = {};
y // 'hello'

对象的默认值生效的条件跟数组的一样,对象的属性值严格等于undefined

js 复制代码
var { x = 3 } = { x : undefined }
x // 3

var { y = 2 } = { y : null }
y // null

注意点

(1) 如果将一个已经声明的变量用于解构赋值, 需要注意

js 复制代码
// 错误写法
let x;
{x} = {x : 1}
// SyntaxError: syntax error

上面的代码会报错, 因为JavaScript 引擎会将 {x} 理解成一个代码块, 从而发生了语法的错误。 只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。后面的圆括号也能解决这个问题。例如:

js 复制代码
// 正确的写法
({x} = {x : 1})

上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行,具体放在下文详细讲解。

(2) 解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

js 复制代码
([] = [ true, false]);
({} = 'abc');
({} = []);

上面的表达式虽然没什么意义,但在语法上是合法的,可以执行。

(3) 由于数组本质是特殊的对象, 因此可以对数组进行对象属性的解构。

js 复制代码
let arr = [1, 2, 3];
let { 0 : first, [arr.length - 1] : last, length : len} = arr;
first // 1
last  // 3
len //  3

上面对arr数组进行解构,但我们用的是{},把数组当成一个对象来解构的,所以数组的下标对应就是相应的属性,而数组对象方法也可以解构出来,比如length

字符串的解构赋值

字符串也可以解构赋值。这是因为字符串被转换成了一个类似数组的对象。

js 复制代码
const [a, b, c, d, e] = 'hello';
a // 'h'
b // 'e'
c // 'l'
d // 'l'
e // 'o' 

类似数组对象有length属性,也可以对字符串对象的这个属性进行解构赋值。

js 复制代码
let {length : len} = 'hello';
len // 5

数值与布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

js 复制代码
let { toString : n } = 123;
s === Number.prototype.toString // true
let { toString : b } = true;
b === Boolean.prototype.toString // true

解构赋值的规则就是, 只要等号右边的值不是对象或数组, 就先将其转为对象。 由于undefinednull 无法转为对象, 所以对它们进行解构赋值,都会报错。

js 复制代码
let { prop: x } = undefined; //TypeError
let { prop: y } = null;      //TypeError

函数参数的解构赋值

函数参数也可以解构赋值。

js 复制代码
function add ([x , y]) {
    return x + y;
}
add([1, 2]);

上面的代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数便被解构成变量xy,可以直接在函数中使用xy

函数参数的解构也可以使用默认值。

js 复制代码
function f({x = 0, y = 0} = {}) {
    return [x, y];
}
f({x : 3, y : 8}); //[3, 8]
f({x : 3}); //[3, 0]
f({}); //[0, 0]
f(); //[0, 0]

function f({x = 0, y = 0} = {})的参数为什么要= {}

js 复制代码
function f({x = 0, y = 0}) {
    return [x, y];
}
f({x : 3, y : 8}); //[3, 8]
f({x : 3}); //[3, 0];
f({}); //[0, 0];
f(); // 报错

我们发现,直接调用f()会报错,因为不传参数时,函数内部收到的参数是 undefinedundefined 进行解构赋值会抛出类型错误。

圆括号问题

首先我们先来介绍一下模式, 在JavaScript解构赋值中,模式(Pattern)指的是等号左侧的变量声明结构,主要分为两种类型:对象模式 数组模式

上面我们知道,如果{}{位于开头,就会被JavaScript解析成代码块,会发生语法错误。而圆括号便能解决这个问题。问题是,如果模式中出现圆括号怎么处理。

ES6 的规则是, 只要有可能导致解构的歧义,就不得使用圆括号。

不能使用圆括号的情况

以下三种解构赋值不得使用圆括号。

(1) 变量声明语句

js 复制代码
let [(a)] = [1];

let {x : (c)} = {};
let ({x : c}) = {};
let {(x : c)} = {};
let ((x): c) = {};

let { o : ({p : p}) } = {o: { p : 2 } };

上面都会报错,因为它们都是变量声明语句,模式不能使用圆括号。

(2) 函数参数

函数参数也属于变量声明,因此不能带有圆括号

js 复制代码
function f([(z)]) {
}

(3) 赋值语句

js 复制代码
({ p: a }) = { p : 42 };
([a]) = [5];

上面代码会报错,因为它将圆括号包裹在了模式之中,导致语法错误。

可以使用圆括号的情况

可以使用圆括号的情况只有一种: 赋值语句的非模式部分, 可以使用圆括号。

js 复制代码
[(b)] = [3]; // 正确
({ p : (d) } = {}); // 正确
[{parseInt.prop}] = [3]; // 正确

上面三行语句都可以正常执行,首先因为它们都是赋值语句,不是声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句,模式是数组是取数组第一个成员,与圆括号无关;第二行模式是p,而不是d;第三行语句与第一行语句的性质一致。

参考 | 推荐

ECMAScript 6 入门 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack

相关推荐
@Dream_Chaser1 分钟前
uniapp ruoyi-app 中使用checkbox 无法选中问题
前端·javascript·uni-app
深耕AI3 分钟前
【教程】在ubuntu安装Edge浏览器
前端·edge
倔强青铜三8 分钟前
苦练Python第4天:Python变量与数据类型入门
前端·后端·python
倔强青铜三16 分钟前
苦练Python第3天:Hello, World! + input()
前端·后端·python
上单带刀不带妹17 分钟前
JavaScript中的Request详解:掌握Fetch API与XMLHttpRequest
开发语言·前端·javascript·ecmascript
倔强青铜三34 分钟前
苦练Python第2天:安装 Python 与设置环境
前端·后端·python
ningmengjing_36 分钟前
在 PyCharm 中安装并配置 Node.js 的指南
开发语言·javascript·ecmascript
我是若尘1 小时前
Webpack 入门到实战 - 复习强化版
前端
晓13131 小时前
JavaScript基础篇——第五章 对象(最终篇)
开发语言·前端·javascript
倔强青铜三1 小时前
苦练Python第1天:为何要在2025年学习Python
前端·后端·python