the-front-end-knowledge-you-may-not-know
the-front-end-knowledge-you-may-not-know copied to clipboard
对象解构可以应用在数组上
1、获取数组的长度:
const {0:a, 2:b, length:l} = ['foo', 'bar', 'baz']
a === 'foo'
b === 'baz'
l === 3
2、还可以使用此技巧获取数据最后一个元素:
const { length: l, [l-1]: last, ...rest } = [1, 2, 3]
l === 3
last === 3
只是 rest 变成了对象 {0: 1, 1: 2}。
3、将数组转换为对象:
> const { ...obj } = [1,2,3]
> obj
{0: 1, 1: 2, 2: 3}
补充一个带默认值:
const {0: a='foolish', 2:b, join:l} = ['foo', 'bar', 'baz']
另外: length/join的这些用法感觉有些神奇。
@jjvein 其实上面这些用法一点都不神奇,数组本身就是魔改版本的对象;而且,数组具有对象(Object)的所有方法,可以通过下面的方式验证:
> Object
ƒ Object() { [native code] }
> Array.prototype.__proto__ === Object.prototype
true
从上面的代码可知,Object 上所有的方法都存储在其原型(Object.prototype)上;Array.prototype 对象的 原型(__proto__) 其实就是指向 Object.prototype,所以,Array 及其 “实例” 拥有 Object 所有属性及特性,所以,解构自然而然地可以应用在数组上。
索引
JavaScript 中并不存在 “真正的数组”,现有的数组是魔改版本的对象,其索引(下标)其实就是不同的属性名称;所以,通过数组的索引(下标)对数组进行解构时,可以看作是取对象对应索引的属性的值。
length
> []
[]
length: 0
将上面的代码放在控制台中运行,输出包括三部分:[]、length: 0 以及 __proto__ 部分。
我们知道,length 是“数组对象”的一个特殊属性,该属性的值通常由引擎在执行增/删数组元素的时候自行维护。所以当数组被放在解构表达式的右边时,数组是被当成一个对象来处理的,所以 let {length: l} = [] 其实就是从 “数组对象 []” 中解构出属性 length 的值并存储在变量 l 中。
不只是 join
解构同操作符 in,会查找解构表达式右边对象的原型链。但是其与 in 操作符有一个不同的地方:in 操作符不会匹配 enumerable: false 的属性;而解构不理会描述符(descriptor),只要要解构的属性在被解构的对象或者原型链上,该属性的值就会被解构然后赋值给对应的变量。所以,不只是 join 属性,其他在 Array.prototype 上的方法如 concat、map、forEach、pop、push、shift、unshift 等也可以被解构:
let {map: m, push: p} = []
m === Array.prototype.map // true
m === [].__proto__.map // true
p === Array.prototype.push // true
p === [].__proto__.push // true
通过 Array 的原型链解析 Object.prototype 上的属性:
Array.prototype.__proto__.hasOwnProperty === Object.prototype.hasOwnProperty // true
let {hasOwnProperty: hop} = []
hop === Object.prototype.hasOwnProperty // true
从上面的例子可以看出,解构操作会沿着当前对象及其原型链一直向上查找,直到 Object.prototype.__proto__ 对象。如果被解构对象及其原型链上不存在该属性,则返回 undefined。
上面的回答整理成了一篇文章,敬请批评指正 在数组上应用对象解构。
文中有对 @justjavac 内容的引用,感谢。
@Tao-Quixote 写的不错,分析的很深入。
JavaScript 中并不存在 “真正的数组”,现有的数组是魔改版本的对象
这个有点那啥了。
按这个逻辑,JavaScript 就没有真正的数组、函数、……了。
- 数组是 Array 的实例,Array 继承自 Object 和 Function。
- 正则表达式是 RegExp 的,RegExp 继承自 Object 和 Function。
- 函数是 Function 的,Function 继承自 Object。
- 异步函数是 AsyncFunction 的,AsyncFunction 继承自 Object。
- ……
大家都是魔改版本的对象。
我再补充一下规范:
至于对象解构为什么可以作用于数组,我们看规范吧 Runtime Semantics: DestructuringAssignmentEvaluation。
其中 ObjectAssignmentPattern 是对象解构,ArrayAssignmentPattern 是数组解构。
with parameter value
ObjectAssignmentPattern : { }
- Let valid be RequireObjectCoercible(value).
- ReturnIfAbrupt(valid).
- Return NormalCompletion(empty).
ObjectAssignmentPattern : { AssignmentPropertyList } { AssignmentPropertyList , }
- Let valid be RequireObjectCoercible(value).
- ReturnIfAbrupt(valid).
- Return the result of performing DestructuringAssignmentEvaluation for AssignmentPropertyList using value as the argument.
第一步是先转换为对象 RequireObjectCoercible ( argument )
| Argument Type | Result |
|---|---|
| Completion Record | If argument is an abrupt completion, return argument. Otherwise return RequireObjectCoercible(argument.[[value]]). |
| Undefined | Throw a TypeError exception. |
| Null | Throw a TypeError exception. |
| Boolean | Return argument. |
| Number | Return argument. |
| String | Return argument. |
| Symbol | Return argument. |
| Object | Return argument. |
所以我们可以试试
> let {__proto__: n} = 1
> n
Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, toString: ƒ, …}
Undefined 和 Null 抛出异常
> let {__proto__: n} = null;
Uncaught TypeError: Cannot destructure property `__proto__` of 'undefined' or 'null'.