DanLevy.net

测验:解构的乐趣

你是解构大师吗?

还是你的 破坏交响曲?

这套测验会检验你对 JavaScript 解构的掌握程度:从“基础”对象语法到嵌套解构以及默认值。还有 TypeScript 与内联类型的加分题!

直接进入热身——证明你的解构功力吧! 👇

这段代码会输出什么?

const person = {
name: 'Dan Levy',
location: 'Cape Town',
};
const { name, age } = person;
console.log(`Name: ${name}, Age: ${age}`);

age 属性在 person 上不存在,所以 age 将是 undefined。绝对不会是 Infinity 😅

结果是:

Name: Dan Levy, Age: undefined

这段代码会做什么?

const person = [ 'Dan Levy', 'Cape Town' ];
const [ name, origin, age ] = person;
console.log(`Name: ${name}, Age: ${age}`);

person 数组没有第三个元素,因此对应的 age 为 undefined。

这将导致:

Name: Dan Levy, Age: undefined

来点嵌套解构吧?

'use strict';
const person = {
name: { first: 'Dan' },
address: { city: 'Denver' },
};
const {
name: { first },
address: { city },
birth: { place },
} = person;
console.log(
`First: ${first}, City: ${place}`,
);

birth: { place } 属性在 person 上不存在,因此会抛出错误。提供嵌套属性的默认值是一种解决方案。

访问嵌套属性时要小心——错误往往不易发现,而且不同浏览器和平台的错误信息各不相同,调试会更具挑战性。

在现代 Chrome 中:TypeError: Cannot read properties of undefined (reading 'place')

在 Node 中也是 TypeError,因为 JavaScript 在读取 place 之前就尝试从 undefined 解构 place。

具体的错误文字会因浏览器和运行时而异。

现在有了一些默认值,这会怎么做?

'use strict';
const person = {
name: { first: 'Dan' },
address: { city: 'Denver' },
};
const {
name: { first = 'Unknown' },
birth: { place = 'Unknown' },
} = person;
console.log(
`Hi ${first} from ${place}`,
);

birth 属性不存在,因此 JavaScript 会尝试从 undefined 解构 place。在 place 的默认值有机会生效之前,就会抛出错误:

TypeError

具体的诊断消息因浏览器和运行时而异。为外层对象提供 = {} 默认值,可以避免从 undefined 解构。

这段代码会做什么?

const person = {
name: { first: 'Dan' },
address: { city: 'Denver' },
};
const {
name: { first = 'Unknown' },
birth: { place = 'Unknown' } = {},
} = person;
console.log(
`Hi ${first} from ${place}`,
);

person 上不存在 birth 属性,所以会回退到一个空对象 = {}。这使得可以使用默认值。

现在把它当作函数参数,会发生什么?

'use strict';
function displayUser({
name = "Unknown",
age = -1,
} = { place: "Unknown" }) {
console.log(`Hi ${name} from ${place}`);
}
displayUser({ name: "Dan" });

这个函数会提取 name 和 age 属性,必要时使用默认值。这里默认对象里的 place 键只是噪音,displayUser() 内并未使用它。

严格模式并不会改变这一点:读取未声明的 place 绑定会抛出 ReferenceError。

undefined 值是如何处理的?

'use strict';
function displayPlace({
name = "N/A",
place = "N/A",
age = -1,
} = { place: "Unknown" }) {
console.log(`${place}`);
}
displayPlace({ name: "Dan" });
displayPlace({ name: "Dan", place: undefined });
displayPlace({ name: "Dan", place: "Joburg" });

省略参数或显式传入 undefined 时,才使用默认参数对象 { place: "Unknown" }:displayPlace() 和 displayPlace(undefined) 都打印 Unknown。

传入对象时,缺失或为 undefined 的 place 属性使用属性默认值 "N/A"。参数默认值与属性默认值作用于不同层级。

结果是:

displayPlace() // Unknown
displayPlace({ name: "Dan" }) // N/A
displayPlace({ name: "Dan", place: undefined }) // N/A

和前一个类似……null 是怎么处理的?

function displayPlace({
name = "N/A",
place = "N/A",
age = -1,
} = { place: "Unknown" }) {
console.log(`${place}`);
}
displayPlace({ name: "Dan", place: null });
displayPlace({ name: "Dan", place: undefined });

第一次调用的 place 为 null,第二次为 undefined。即使已经传入对象,只要 place 缺失或为 undefined,属性默认值 "N/A" 就会生效。null 不会触发默认值,因此依次输出 null 和 N/A。

TypeScript 在前方

现在在 TypeScript 中…… 这会怎么做?

'use strict';
function displayPlace(
{
name = 'N/A',
place = 'N/A',
}: {
name: string;
place: string;
age: number;
},
) {
console.log(`${place}`);
}
displayPlace({ name: 'Dan', place: null });

TypeScript 报告错误,因为 place 的类型被标记为 string,但调用时传入了 null。调用还省略了必需的 age 属性。

如果忽略类型错误,运行代码会在控制台打印 null。

我们来试试重命名/赋值吧……

'use strict';
function displayPlace({
name = 'N/A',
place: location = 'N/A',
}: {
name: string;
place: string;
age?: number;
}) {
console.log(`${location}`);
}
displayPlace({ name: 'Dan', place: 'Denver' });

这将在控制台打印 Denver。函数签名中将 place 属性重命名为 location。在适配第三方数据结构时,这是一种常见模式(在解构时重命名属性)。

找出类型错误:

function greet({
name: {first = "N/A", last = "N/A"},
birth: {place = "N/A"} = {},
age = -1,
}: {
name: {first?: string, last?: string};
birth: {place?: string};
age: number;
}) {
console.log(`Hi ${first} ${last} from ${place}`);
}
greet({ name: {first: 'Dan'} });

错误出现在 greet 函数签名中。传入的对象缺少 age 和 birth 属性,因此在类型定义里它们应当是可选的。

即使 birth 属性使用了默认值进行解构,类型定义仍要求它必须存在。要在 TypeScript 中将属性标记为可选,需要使用 ? 运算符。

注意 birth?: { place?: string } 并不等同于 birth: { place?: string } | undefined。

现在使用 赋值(注意 f、l 和 p 变量)

'use strict';
function greet(
{
name: {first: f = "N/A", last: l = "N/A"},
birth: {place: p = "N/A"} = {},
age = -1,
}: {
name: {first?: string, last?: string};
birth?: {place?: string};
age?: number;
}
) {
console.log(`Hi ${f} ${l} from ${place}`);
// What will 👆 do?
}
greet({
name: {first: 'Dan', last: 'Levy'},
birth: {place: 'Cape Town'},
});

又一次错误!你开始猜测了,是吧?

读取带有默认值、赋值和类型的多层解构真是困难!

一旦 place 被重新赋值给 p 变量,它就在 console.log 语句的作用域中不再定义。

console.log(`Hi ${f} ${l} from ${place}`); // ❌
// to:
console.log(`Hi ${f} ${l} from ${p}`); // ✅

还想继续挑战?来看看我的测验合集,体验更多有趣的题目!