Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
justjavac opened this issue Nov 22, 2017 · 3 comments
Open

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

justjavac opened this issue Nov 22, 2017 · 3 comments

Comments

@justjavac
Copy link
Owner

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}
@jjvein
Copy link

jjvein commented Mar 8, 2018

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

@Tao-Quixote
Copy link

@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 内容的引用,感谢。

@justjavac
Copy link
Owner Author

justjavac commented Apr 9, 2018

@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'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants