解构赋值-对象和函数参数
所谓解构赋值,就是一种模式匹配规则,当等号两边的模式相同时,就可以进行赋值操作。解构赋值的本质,就在于赋值二字。
对象的解构赋值
1. 变量名和属性名相同
对象的解构赋值要求被赋值变量名与赋值对象属性名相同,才可以得到正确的值。
const { name, age, job } = { name: "jaakko", age: 22 }
name // "a"
age // 10
job // undefined
上面这个例子中,变量 name 和 age 都与赋值对象中属性名相同,故赋值成功,而变量 job 在赋值对象中没有对应属性名称,故赋值失败,值为 undefined。
2. 变量名和属性名不同
如果不想使用原对象的属性名作为变量名,就要用到下面这种写法。
const { name: myName, age: myAge } = { name: "jaakko", age: 22 }
myName // "a"
myAge // 10
这也说明默认的赋值操作实际是下面这种写法的简写。
const { name: name, age: age } = { name: "jaakko", age: 22 }
3. 指定默认值
上面我们说到,当被赋值变量与赋值对象属性名没有对应时,变量会被赋值为 undefined,为了避免在赋值失败时变量变为 undefined,我们可以给它指定默认值。
当赋值失败时,变量的值就是我们指定的默认值;当赋值成功时,变量会忽略我们指定的默认值。
const { name="fake_name" } = { name: "jaakko" };
name // "jaakko"
上面这个例子中,name 属性被解构成功,所以忽略了默认值。
注意,这里设置默认值使用的是
=,而不是:。
默认值生效的条件是,对象的属性值严格等于 undefined。
const { name="fake_name", age = 22, job = "engineer" } = {
name: "jaakko",
age: undefined,
job: null
};
name // "jaakko"
age // 22
job // null
上面这个例子中,name 由于解构成功而忽略默认值,age 得到了 undefined 所以使用默认值,而 job 属性的值为 null,不严格等于 undefined,所以算解构成功。
函数参数的解构赋值
1. 函数参数为数组时
function add([x, y]) {
return x + y;
}
add([3, 4]); // 7
传递给 add 方法的参数是一个数组,但对于 add 内部来说,它们能感受到的就是 x、y 两个变量。开篇提到过,解构赋值的本质就是给变量赋值。
2. 函数参数为对象时
function introduce({name, age}) {
console.log(`我叫 ${name}, 我 ${age} 岁!`);
}
introduce({name: 'jaakko', age: 22}) // "我叫 jaakko, 我 22 岁!"
和数组同理,函数内部接收变量。
给变量设定默认值
函数参数的解构也可以指定默认值。
function introduce({name = "fake_name", age = 99}) {
console.log(`我叫 ${name}, 我 ${age} 岁!`);
}
introduce({}) // "我叫 fake_name, 我 99 岁!"
当 introduce 方法的参数为空对象时,name 和 age 解构失败,使用默认值。
下面这种情况会报错。
function introduce({name = "fake_name", age = 99}) {
console.log(`我叫 ${name}, 我 ${age} 岁!`);
}
introduce(); // Uncaught TypeError: Cannot destructure property `name` of 'undefined' or 'null'.
当 introduce 的参数为空时,就会发生错误(注意!不是解构失败,是抛出错误)。造成错误的原因是,我们试图从一个不存在的值中去解构,这显然是错误的行为。
为了避免这种情况,我们需要给 introduce 的参数一个默认值。
function introduce({name = "fake_name", age = 99} = {}) {
console.log(`我叫 ${name}, 我 ${age} 岁!`); // "我叫 fake_name, 我 99 岁!"
}
introduce() // "我叫 fake_name, 我 99 岁!"
现在,即使不给 introduce 传递参数也不会报错了,因为参数有一个默认值,是一个空对象。
给函数参数设定默认值
最后,要注意下面这种情况。
function introduce({name, age} = {name: "fake_name", age: 99}) {
console.log(`我叫 ${name}, 我 ${age} 岁!`); // "我叫 fake_name, 我 99 岁!"
}
introduce({ name: "jaakko", age: 22 }); // 我叫 jaakko, 我 22 岁!
introduce({}); // 我叫 undefined, 我 undefined 岁!
introduce(); // 我叫 fake_name, 我 99 岁!
注意看第二次调用 introduce 方法的结果,似乎与我们预期的不太一样,下面我们依次来分析这三种调用方式的不同。
(1) 带参调用
这个结果与我们的预期结果相符:传入参数,解构成功,忽略默认值,函数内部使用传入参数。
(2) 传入参数为空对象
这种情况就与我们的预期不符了,我们预期是:解构失败,使用参数默认值。这里明显是解构失败了,因为参数的值均为 undefined,但是却没有使用默认值,为什么呢?
还记得我们上一个例子吗?我们说给函数参数一个默认值(空对象),也就是说函数参数中 = 后面的部分应该是函数的默认参数。
{name, age} = {name: "fake_name", age: 99}
现在函数的默认参数是这个带参数的对象 {name: "fake_name", age: "99"},所以,当我们不给函数传递参数时,函数才会使用这个默认参数。
现在的情况是,我们给函数传递了一个空对象 {} 作为参数,所以函数会从传递进来的参数中去解构 name 和 age 的值,而不是使用默认值。
(3) 不带参调用
理解了第 (2) 种情况,这种情况就好理解了。
不给函数传递参数时,会使用默认参数作为函数的参数,会从默认参数对象去解构变量,所以这里 name 和 age 解构出来的是函数默认参数的值。
总结
理解了上面三种情况产生的原因(重点是后两种),我们应该明白了,我们给函数设定默认值和给变量设定默认值是两种完全不同的行为,在使用的时候一定要注意。
推荐下面这种写法。
function introduce({name = "fake_name", age = 99} = {}) {
console.log(`我叫 ${name}, 我 ${age} 岁!`); // "我叫 fake_name, 我 99 岁!"
}
introduce({ name: "jaakko", age: 22 }); // 我叫 jaakko, 我 22 岁!
introduce({}); // 我叫 fake_name, 我 99 岁!
introduce(); // 我叫 fake_name, 我 99 岁!
这种写法得到的结果与我们预期相符。
简单分析一下:
给 introduce 方法设定的默认参数是一个空对象 {}。
- 当带参数调用时,忽略默认参数(空对象),变量解构成功,忽略变量默认值,使用解构的值。
- 当传入参数为空对象时,忽略默认参数(传入参数和默认参数都是空对象),变量解构失败,使用变量默认值。
- 当参数为空时(即不传参数),使用默认参数(空对象)作为参数,解构失败,使用变量默认值。
参考: