扩展运算符(spread)是三个点(...
)。
对于数组来说,它好比 rest
参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
该运算符主要用于函数调用。
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
上面代码中,array.push(...items)
和add(...numbers)
这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。
扩展运算符与正常的函数参数可以结合使用,非常灵活。
function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);
扩展运算符后面还可以放置表达式。
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[], 1]
// [1]
注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
上面三种情况,扩展运算符都放在圆括号里面,但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。
复制数组:
var arr1 = [1, 2];
var arr2 = [...arr1];
console.log(arr2); //[1, 2]
合并数组:
var arr1 = [1, 2];
var arr2 = [3, 4];
var arr3 = [...arr1, ...arr2];
console.log(arr3); //[1, 2, 3, 4]
扩展运算符可以与解构赋值结合起来,用于生成数组:
var [a, ...b] = [1, 2, 3];
console.log(a); //1
console.log(b); //[2, 3]
var [c, ...d] = [];
console.log(c); //undefined
console.log(d); //[]
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
var [...a, b] = [1, 2, 3]; //报错
var [c, ...d, e] = [4, 5, 6]; //报错
扩展运算符还可以将字符串转为真正的数组。
[...'hello']
//['h', 'e', 'l', 'l', 'o']
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
对于对象来说,它可以取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
如果扩展运算符后面是一个空对象,则没有任何效果。
{...{}, a: 1}
// { a: 1 }
如果扩展运算符后面不是对象,则会自动将其转为对象。
//等同于 {...Object(1)}
{...1} //{}
上面代码中,扩展运算符后面是整数1,会自动转为数值的包装对象Number{1}
。由于该对象没有自身属性,所以返回一个空对象。
下面的例子都是类似的道理。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
但是,如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
对象的扩展运算符等同于使用Object.assign()
方法。
var obj1 = { ...obj };
// 等同于
var obj2 = Object.assign({}, obj);
上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。
// 写法一
var obj1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 写法二
var obj2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法三
var obj3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
扩展运算符可以用于合并两个对象。
let obj = { ...obj1, ...obj2 };
// 等同于
let obj = Object.assign({}, obj1, obj2);
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
var obj = {a: 1, b: 2};
var obj1 = {...obj, a: 3, b:4}; //obj1 {a: 3, b: 4}
// 等同于
var obj2 = {...obj, ...{a: 3, b: 4}};
// 等同于
var a = 3, b = 4, obj3 = {...obj, a, b};
// 等同于
var obj4 = Object.assign({}, obj1, {x: 1, y: 2});
与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。
var obj = {...(2 > 1 ? {a: 1} : {}), b: 2};
//{a: 1, b: 2}
扩展运算符可以与解构赋值使用,将目标对象自身的所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。
var {a, b, ...c} = {a: 1, b: 2, x: 3, y: 4};
a //1
b //2
c //{x: 3, y: 4}
解构赋值必须是最后一个参数,否则会报错。
var {...a, b} = {x: 1, y: 2, b: 3}; //报错
扩展运算符的解构赋值,不能复制继承自原型对象的属性。
var obj1 = {a: 1};
var obj2 = {b: 2};
obj2.__proto__ = obj1;
var {...obj3} = obj2;
obj3 //{b: 2}