测验:解构的乐趣
你是解构大师吗?
还是你的 破坏交响曲?
这套测验会检验你对 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 属性在 person 上不存在,所以整个对象仍然需要一个默认值,而不仅仅是嵌套属性。基本上缺少一个 = {} 的默认值。
这样写的意思是“如果 person.birth 为 undefined,则 place 为 Unknown”。但 person.birth 为 undefined,于是尝试对 undefined 进行解构,导致错误。
In modern Chrome: `TypeError: Cannot read properties of undefined (reading 'place')`
In Node, this is also a `TypeError` because JavaScript tries to destructure `place` from `undefined`.
Exact wording varies between browsers and runtimes.现在把它当作函数参数,会发生什么?
'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" });函数 displayPlace 只会在没有传入对象时使用默认对象。因此,唯一能得到 { place: "Unknown" } 默认值的方式是调用 displayPlace() 而不传参。
另一个值得注意的行为是,如果为 place 传入 undefined,默认值也会被使用,这有点类似于 JSON.stringify 的行为(会忽略 undefined,但会识别 null)。
结果是:
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。只有当整个对象缺失 或 为 undefined 时,place 的默认值才会生效。null 会原样传递为 null。
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}`); // ✅