the-front-end-knowledge-you-may-not-know icon indicating copy to clipboard operation
the-front-end-knowledge-you-may-not-know copied to clipboard

对象解构可以应用在数组上

Open justjavac opened this issue 7 years ago • 3 comments

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}

justjavac avatar Nov 22 '17 02:11 justjavac

补充一个带默认值: const {0: a='foolish', 2:b, join:l} = ['foo', 'bar', 'baz'] 另外: length/join的这些用法感觉有些神奇。

jjvein avatar Mar 08 '18 08:03 jjvein

@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 上的方法如 concatmapforEachpoppushshiftunshift 等也可以被解构:

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 avatar Apr 09 '18 08:04 Tao-Quixote

@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 : { }

  1. Let valid be RequireObjectCoercible(value).
  2. ReturnIfAbrupt(valid).
  3. Return NormalCompletion(empty).

ObjectAssignmentPattern : { AssignmentPropertyList } { AssignmentPropertyList , }

  1. Let valid be RequireObjectCoercible(value).
  2. ReturnIfAbrupt(valid).
  3. 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'.

justjavac avatar Apr 09 '18 09:04 justjavac