测验:解构的乐趣
你是解构大师吗?
还是你的 破坏交响曲?
这套测验会检验你对 JavaScript 解构的掌握程度:从“基础”对象语法到嵌套解构以及默认值。还有 TypeScript 与内联类型的加分题!
直接进入热身——证明你的解构功力吧! 👇
来点嵌套解构吧?
'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 解构。
现在把它当作函数参数,会发生什么?
'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() // UnknowndisplayPlace({ name: "Dan" }) // N/AdisplayPlace({ 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}`); // ✅还想继续挑战?来看看我的测验合集,体验更多有趣的题目!
