1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
---
title: 展开语法
slug: Web/JavaScript/Reference/Operators/Spread_syntax
tags:
- ECMAScript2015
- Iterator
- JavaScript
- 展开
translation_of: Web/JavaScript/Reference/Operators/Spread_syntax
---
<div>{{jsSidebar("Operators")}}</div>
<div><strong>展开语法(Spread syntax), </strong>可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。(<strong>译者注</strong>: 字面量一般指 <code>[1, 2, 3]</code> 或者 <code>{name: "mdn"}</code> 这种简洁的构造方式)</div>
<div>{{EmbedInteractiveExample("pages/js/expressions-spreadsyntax.html")}}</div>
<h2 id="语法">语法</h2>
<p>函数调用:</p>
<pre class="syntaxbox">myFunction(...iterableObj);</pre>
<p>字面量数组构造或字符串:</p>
<pre>[...iterableObj, '4', ...'hello', 6];</pre>
<p>构造字面量对象时,进行克隆或者属性拷贝(ECMAScript 2018规范新增特性):</p>
<pre class="syntaxbox">let objClone = { ...obj };</pre>
<h2 id="示例">示例</h2>
<h3 id="在函数调用时使用展开语法">在函数调用时使用展开语法</h3>
<h4 id="等价于apply的方式">等价于apply的方式</h4>
<p>如果想将数组元素迭代为函数参数,一般使用{{jsxref( "Function.prototype.apply")}} 的方式进行调用。</p>
<pre class="brush: js">function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);</pre>
<p>有了展开语法,可以这样写:</p>
<pre class="brush: js"><code>function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);</code></pre>
<p>所有参数都可以通过展开语法来传值,也不限制多次使用展开语法。</p>
<pre class="brush: js">function myFunction(v, w, x, y, z) { }
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);</pre>
<h4 id="在_new_表达式中应用">在 new 表达式中应用</h4>
<p>使用 <code>new</code> 关键字来调用构造函数时,不能<strong>直接</strong>使用数组+ <code>apply</code> 的方式(<code>apply</code> 执行的是调用 <code>[[Call]]</code> , 而不是构造 <code>[[Construct]]</code>)。当然, 有了展开语法, 将数组展开为构造函数的参数就很简单了:</p>
<pre class="brush: js">var dateFields = [1970, 0, 1]; // 1970年1月1日
var d = new Date(...dateFields);</font></font>
</pre>
<p>如果不使用展开语法, 想将数组元素传给构造函数, 实现方式可能是这样的:</p>
<pre class="brush: js">function applyAndNew(constructor, args) {
function partial () {
return constructor.apply(this, args);
};
if (typeof constructor.prototype === "object") {
partial.prototype = Object.create(constructor.prototype);
}
return partial;
}
function myConstructor () {
console.log("arguments.length: " + arguments.length);
console.log(arguments);
this.prop1="val1";
this.prop2="val2";
};
var myArguments = ["hi", "how", "are", "you", "mr", null];
var myConstructorWithArguments = applyAndNew(myConstructor, myArguments);
console.log(new myConstructorWithArguments);
// (myConstructor构造函数中): arguments.length: 6
// (myConstructor构造函数中): ["hi", "how", "are", "you", "mr", null]
// ("new myConstructorWithArguments"中): {prop1: "val1", prop2: "val2"}</pre>
<h3 id="构造字面量数组时使用展开语法">构造字面量数组时使用展开语法</h3>
<h4 id="构造字面量数组时更给力!">构造字面量数组时更给力!</h4>
<p>没有展开语法的时候,只能组合使用 <code>push</code>, <code>splice</code>, <code>concat</code> 等方法,来将已有数组元素变成新数组的一部分。有了展开语法, 通过字面量方式, 构造新数组会变得更简单、更优雅:</p>
<pre class="brush: js">var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes']; </font></font>
// ["head", "shoulders", "knees", "and", "toes"]
</pre>
<p>和参数列表的展开类似, <code>...</code> 在构造字面量数组时, 可以在任意位置多次使用.</p>
<h4 id="数组拷贝copy">数组拷贝(copy)</h4>
<pre class="brush: js">var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);
// arr2 此时变成 [1, 2, 3, 4]
// arr 不受影响
</pre>
<p><strong>提示:</strong> 实际上, 展开语法和 {{jsxref("Object.assign()")}} 行为一致, 执行的都是浅拷贝(只遍历一层)。如果想对多维数组进行深拷贝, 下面的示例就有些问题了。</p>
<pre class="brush: js">var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
// Now array a is affected as well: [[2], [3]]
</pre>
<h4 id="连接多个数组">连接多个数组</h4>
<p>{{jsxref("Array.concat")}} 函数常用于将一个数组连接到另一个数组的后面。如果不使用展开语法, 代码可能是下面这样的:</p>
<pre class="brush: js">var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将 arr2 中所有元素附加到 arr1 后面并返回
var arr3 = arr1.concat(arr2);</pre>
<p>使用展开语法:</p>
<pre class="brush: js">var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2];
</pre>
<p>{{jsxref("Array.unshift")}} 方法常用于在数组的开头插入新元素/数组. 不使用展开语法, 示例如下:</p>
<pre class="brush: js">var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将 arr2 中的元素插入到 arr1 的开头
Array.prototype.unshift.apply(arr1, arr2) // arr1 现在是 [3, 4, 5, 0, 1, 2]</pre>
<p>如果使用展开语法, 代码如下: [请注意, 这里使用展开语法创建了一个新的 <code>arr1</code> 数组, {{jsxref("Array.unshift")}} 方法则是修改了原本存在的 <code>arr1</code> 数组]:</p>
<pre class="brush: js">var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1]; // arr1 现在为 [3, 4, 5, 0, 1, 2]
</pre>
<h3 id="构造字面量对象时使用展开语法">构造字面量对象时使用展开语法</h3>
<p><a href="https://github.com/tc39/proposal-object-rest-spread">Rest/Spread Properties for ECMAScript</a> 提议(stage 4) 对 <a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/Object_initializer">字面量对象</a> 增加了展开特性。其行为是, 将已有对象的所有可枚举(enumerable)属性拷贝到新构造的对象中.</p>
<p>浅拷贝(Shallow-cloning, 不包含 prototype) 和对象合并, 可以使用更简短的展开语法。而不必再使用 {{jsxref("Object.assign()")}} 方式.</p>
<pre class="brush: js">var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }
</pre>
<p><strong>提示</strong>: {{jsxref("Object.assign()")}} 函数会触发 <a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/set">setters</a>,而展开语法则不会。</p>
<p><strong>提示</strong>: 不能替换或者模拟 {{jsxref("Object.assign()")}} 函数:</p>
<pre><code>var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
const merge = ( ...objects ) => ( { ...objects } );
var mergedObj = merge ( obj1, obj2);
// Object { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }
var mergedObj = merge ( {}, obj1, obj2);
// Object { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }</code></pre>
<p>在这段代码中, 展开操作符(spread operator)并没有按预期的方式执行: 而是先将多个解构变为剩余参数(rest parameter), 然后再将剩余参数展开为字面量对象.</p>
<h3 id="只能用于可迭代对象">只能用于可迭代对象</h3>
<p>在数组或函数参数中使用展开语法时, 该语法只能用于 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator">可迭代对象</a>:</p>
<pre class="brush: js">var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable
</pre>
<h3 id="展开多个值">展开多个值</h3>
<p>在函数调用时使用展开语法,请注意不能超过 JavaScript 引擎限制的最大参数个数。更多详细信息,请参考: <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply" title="apply()方法使用给定的这个值调用一个函数,并以一个数组(或类似数组的对象)的形式提供参数。"><code>apply()</code></a>。</p>
<h2 id="剩余语法(剩余参数)">剩余语法(剩余参数)</h2>
<p>剩余语法(Rest syntax) 看起来和展开语法完全相同,不同点在于, 剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来并“凝聚”为单个元素。 请参考: <a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters">剩余参数</a>。</p>
<h2 id="规范">规范</h2>
<table class="standard-table">
<thead>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{SpecName('ES2015', '#sec-array-initializer')}}</td>
<td>{{Spec2('ES2015')}}</td>
<td>Defined in several sections of the specification: <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-array-initializer">Array Initializer</a>, <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-argument-lists">Argument Lists</a></td>
</tr>
<tr>
<td>{{SpecName('ESDraft', '#sec-array-initializer')}}</td>
<td>{{Spec2('ESDraft')}}</td>
<td>No changes.</td>
</tr>
<tr>
<td>{{SpecName('ESDraft', '#sec-object-initializer')}}</td>
<td>{{Spec2('ESDraft')}}</td>
<td>Defined in <a href="https://tc39.github.io/ecma262/2018/#sec-object-initializer">Object Initializer</a></td>
</tr>
</tbody>
</table>
<h2 id="浏览器兼容性">浏览器兼容性</h2>
<p>{{Compat("javascript.operators.spread")}}</p>
<h2 id="相关链接">相关链接</h2>
<ul>
<li><a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters">剩余参数</a>(Rest Parameters也使用' <code>...</code> ')</li>
</ul>
|