ES6——扩展运算符的作用以及使用场景
文章目录
- 对象扩展运算符
- 解构赋值
- 扩展运算符
- 数组扩展运算符
- 替代函数的apply() 方法
- 扩展运算符的应用
- 复制数组
- 合并数组
- 与解构赋值结合
- 字符串
- 实现了Iterator接口的对象
对象扩展运算符
解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的
、但尚未被读取的属性
,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
//1、
let {x,y,...z} = {x:1,y:2,a:3,b:4}
console.log(x,y,z)
x // 1
y // 2
z // {a: 3, b: 4}
//2、
let {x,a,...z} = {x:1,y:2,a:3,b:4}
console.log(x,a,z)
x // 1
a // 3
z // {y: 2, b: 4}
上面这段代码,变量z是解构赋值所在的对象。它获取等号右边的所有尚未读取的键(a和b或者y和b),将它们连同值一起拷贝过来。
由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
解构赋值必须是最后一个参数,否则会报错
let { ...x, y, z } = {y:2,z:3,a:1,b:2}
let { x, ...y, ...z } = {y:2,z:3,a:1,b:2}
cosnole.log(x,y,z)
//报错:Uncaught SyntaxError: Rest element must be last element
注意:解构赋值是浅拷贝
,如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: {b : 1 }};
let { ...x } = obj;
//改变原对象obj的a属性,因为a属性引用了一个对象,修改这个对象的值,会影响到解构赋值对它的引用。
obj.a.b = 2
console.log(x.a.b) // 2
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性
let o1 = { a:1 }
let o2 = { b:2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
console.log(o2)
console.log(o3) //{b: 2}
console.log(o3.a) //undefined
上面代码中,对象o3复制了o2,但是只复制了o2自身的属性,没有复制它的原型对象o1的属性。
解构赋值的一个用处,是扩展某个函数的参数,引入其他操作
function baseFunction({ a, b }) {// ...
}
function wrapperFunction({ x, y, ...restConfig }) {// 使用 x 和 y 参数进行操作// 其余参数传给原始函数return baseFunction(restConfig);
}
上面代码中,原始函数baseFunction接受a和b作为参数,函数wrapperFunction在baseFunction的基础上进行了扩展,能够接受多余的参数,并且保留原始函数的行为。
扩展运算符
对象的扩展运算符(…)用于取出参数对象的所有的可遍历属性,拷贝到当前对象之中。
同理如果参数对象中的属性为引用类型,那么扩展运算符实现的复制仍然是浅拷贝。
let z = { a:3, b:4 };
let n = {...z}
n // {a: 3, b: 4}
// 属性是引用类型
let z = { a:3, b:{name:"abc"} };
let n = {...z}
n.b.name = "zzz"
console.log(z,n)
// {a:3,b:{name: 'zzz'}} {a:3,b:{name: 'zzz'}}
由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] };
foo; // {0: 'a', 1: 'b', 2: 'c'}
foo[0]; // 'a'
如果扩展运算符后面是一个空对象,则没有任何效果。
{...{}, a: 1}
// { a: 1 }
如果扩展运算符后面不是对象,则会自动将其转为对象。
{...1} // {}
// 等同于 {...Object(1)}
// 扩展运算符后面是整数1,会自动转为数值的包装对象Number{1}.由于该对象没有自身属性,所以返回一个空对象。
下面也是一个道理
// 等同于 {...Object(true)}
{...true} // {}// 等同于 {...Object(undefined)}
{...undefined} // {}// 等同于 {...Object(null)}
{...null} // {}
但是如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
{...'wyxxxl'}
// {0: 'w', 1: 'y', 2: 'x', 3: 'x', 4: 'x', 5: 'l'}
类的方法都定义在prototype对象上面
对象的扩展运算符,只会返回参数对象自身的、可枚举的属性,这一点要特别小心,尤其是用于类的实例对象时。
class C {p = 12;m() {}
}
let c = new C();
let clone = { ...c };clone.p; //12
clone.m(); //报错
上面示例中,c是C类的实例对象,对其进行扩展运算时,只会返回c自身的属性c.p,而不会返回c的方法c.m(),因为这个方法定义在C的原型对象上。
对象的扩展运算符等同于使用Object.assign()方法(浅拷贝)。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
扩展运算符可以用于合并两个对象
let a = {x:1,y:2}
let b = {z:3}
let ab = {...a,...b }
let abc = Object.assign({},a,b);
console.log(ab) // {x: 1, y: 2, z: 3}
console.log(abc) // {x: 1, y: 2, z: 3}
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
let a = {x:1,y:2}
let aWithOverrides = { ...a, x: 3, y: 4 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 3, y: 4 } };
// 等同于
let x = 3, y = 4, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 3, y: 4 });
console.log(aWithOverrides) // {x: 3, y: 4}
上面代码中,a对象的x属性和y属性,拷贝到新对象后会被覆盖掉。
然后我们就可以发现,这用来修改现有对象部分的属性就很方便了。
let newVersion = {...previousVersion,name: 'New Name'
};
我们可以看到上面代码newVersion
对象复制了previousVersion
对象的原有属性,并且重新赋值了name
属性。
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
let a = { y:3 }
let aWithDefaults = {x:1,y:2,...a}
aWithDefaults // {x: 1, y: 3}
//等同于
let aWithDefaults = Object.assign({},{ x:1,y:2 },a)
let aWithDefaults = Object.assign({ x:1,y:2},a)
对象的扩展运算符后面可以跟表达式
let x = 0
const obj = {...(x > 1 ? {a:1} : {}),b:2
}
obj // { b : 2}
数组扩展运算符
数组的扩展运算符,可以用来将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
该运算符可以用于函数调用
//1、
function push(arr,...items){arr.push(...items);return arr;
}
let arr2 = [3,4,5]
push([1,2],...arr2) // [1, 2, 3, 4, 5]
//2、
function add(x,y){return x + y;
}
const nums = [1,10]
add(...nums) // 11
扩展运算符与正常的函数参数可以结合使用,非常灵活
function fun(v,w,x,y,z){ }
const args = [0,1];
fun(-1,...args,2,3,...[5]);
扩展运算符后面还可以放置表达式
let x = 1;
const arr = [...(x > 0 ? ['a'] : []),'b','c',
];
arr; // ['a', 'b', 'c']
如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[], 1]
// [1]
注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
上面三种情况,扩展运算符都放在圆括号里面,但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。
替代函数的apply() 方法
由于扩展运算符可以展开数组,所以不再需要apply() 方法将数组转为函数的参数了。
// ES5 的写法
function f(x, y, z) {// ...
}
var args = [0, 1, 2];
f.apply(null, args);// ES6 的写法
function f(x,y,z){// ...
}
let args = [0,1,2]
f(...args);
下面是扩展运算符取代apply()
方法的一个实际例子.
应用Math.max( )方法,简化求出一个数组最大元素的写法。
Math.max.apply(null,[14,3,60]);Math.max(...[14,3,60]);
//等同于
Math.max(14,3,60);
由于JS不提供 求数组最大元素的函数,所以只能套用Math.max( )函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符后,就可以直接使用Math.max() 了。
另一个例子是用通过push( )
函数,将一个数组添加到另一个数组的尾部。
let arr1 = [0,1,2];
let arr2 = [3,4,5];
arr1.push(...arr2);
扩展运算符的应用
复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1,2];
const a2 = a1; //此时a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。
ES5只能用变通方法来复制数组(这边发现一个问题)
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
上面代码中,a1会返回原数组的克隆,再修改a2就不会对a1产生影响。
(仅限于数组中的元素为基本数据类型,如果数组中的元素存在引用类型,那个拷贝的是引用数据类型的指针,所以修改新数组原数组还是会跟着修改
)
如果不提供参数,concat()方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是如果数组成员包括复合类型的值(比如对象),则新数组拷贝的是该值的引用
const a1 = [1, {msg:"hhh"}];
const a2 = a1.concat();
a2[1].msg = "love you";
a1 //[1, {msg:"love you"}]
同样扩展运算符提供了复制数组的简便写法,但依旧是浅拷贝
const a1 = [1,{msg:"hhh"}]
const a2 = [...a1]
a2[1].msg = "love you too"
a1 //[1,{msg:"love you too"}]
合并数组
扩展运算符提供了数组合并的新写法。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
不过,这两种方法都是浅拷贝,使用的时候需要注意。
const a1 = [{foo:1}]
const a2 = [{bar:2}]
const a3 = a1.concat(a2)
const a4 = [...a1,...a2]
console.log(a3,a4) // [{foo: 1}, {bar: 2}] [{foo: 1}, {bar: 2}]
a3[0] === a1[0] // true
a4[0] === a1[0] // true
上面代码中,a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组
const [first,...rest] = [1,2,3,4]
first //1
rest //[2,3,4]const [first, ...rest] = [];
first // undefined
rest // []const [first, ...rest] = ["foo"];
first // "foo"
rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
字符串
扩展运算符还可以将字符串转为真正的数组
[..."hello"] // ['h', 'e', 'l', 'l', 'o']
上面的写法,有一个重要的好处,那就是能够正确识别四个字节的 Unicode 字符。
'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3
上面代码的第一种写法,JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。
function length(str) {return [...str].length;
}
length('x\uD83D\uDE80y') // 3
凡是涉及到操作四个字节的 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。
let str = 'x\uD83D\uDE80y';str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'[...str].reverse().join('')
// 'y\uD83D\uDE80x'
上面代码中,如果不用扩展运算符,字符串的reverse()操作就不正确。
实现了Iterator接口的对象
任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
上面代码中,querySelectorAll()
方法返回的是一个NodeList
对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList
对象实现了 Iterator。
(6)Map 和 Set 结构,Generator 函数
扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。
let map = new Map([[1, 'one'],[2, 'two'],[3, 'three'],
]);let arr = [...map.keys()]; // [1, 2, 3]
Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
const go = function*(){yield 1;yield 2;yield 3;
};[...go()] // [1, 2, 3]
上面代码中,变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
ES6——扩展运算符的作用以及使用场景相关推荐
- Javascript中扩展运算符的作用及使用场景
扩展运算符(-)是ES6新增的一个运算符,下面来介绍一下它的使用场景及作用 1.作为函数的形参 在作为函数的形参时,通过 -数组名 来表示,也称为rest参数,当函数被调用时传入的实参全部会被放入到这 ...
- 扩展运算符的作用及使用场景
(1)对象扩展运算符 对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. let bar = { a: 1, b: 2 }; let baz = { ...bar }; ...
- 扩展运算符的作用以及使用场景
扩展运算符 对象的扩展运算符(-)用于取出参数对象中的所有可遍历属性,拷贝到当前对象中 在JS中,扩展运算符(spread)是三个点-,剩余运算符(rest)同为三个点(-) 我们可以把-理解为展开运 ...
- ES6扩展运算符的几个小技巧
es6扩展运算符,也就是... ,作用是将一个数组转为用逗号分隔的参数序列. 那么问题来了,我们为啥要用这么奇怪的东东涅,当然因为它能大大提高我们的开发效率.所以,可别小看这三个点. 1.复制数组( ...
- ES6 - 扩展运算符与Object.assign对象拷贝与合并
文章目录 扩展运算符能做什么? 什么是深浅拷贝 使用ES6扩展运算符 对象浅拷贝 对象合并 Object.assign Object.assign详解 Object.assign()实用 给对象添加属 ...
- ES6——扩展运算符(...)
文章目录 一.扩展运算符(...) 二.数组扩展运算符的应用 1.合并数组 2.与解构赋值结合 3.字符串转数组 4.实现了 Iterator 接口的对象 5.Map 和 Set 结构, Genera ...
- php es6写法,ES6...扩展运算符(示例代码)
在数组中的应用 扩展运算符(spread)是三个点(...).它好比 rest 参数的逆运算,++将一个数组转为用逗号分隔的参数序列++. console.log(...[1, 2, 3]) // 1 ...
- js数组合并、去重、降维(ES6:扩展运算符、Set)
js数组合并.去重.降维 1.合并 1.1使用concat()进行合并数组 function get_concat(collection_a, collection_b) {return collec ...
- JS之ES6扩展运算符三个点(...)用于剥离{} 获取属性值
1.用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中 let bar = { a: 1, b: 2 }; let baz = { ...bar }; // { a: 1, b: 2 } 等价于 ...
最新文章
- Windows客户端C/C++编程规范“建议”——结构
- Django常用模板标签
- 【转】eclipse android 设置及修改生成apk的签名文件 -- custom debug keystore
- 工业界推荐系统实用分析技巧
- REST framework 权限管理源码分析
- matlab gui实例_App Designer 自学实例8
- Python里面search()和match()的区别?
- 力扣104. 二叉树的最大深度(JavaScript)
- makefile高级应用
- 微软私有云分享(R2)21 BMC提升B格
- [日常] 最近的一些破事w...
- AcWing 905. 区间选点(贪心)
- win7程序员御用主题包制作
- DCGAN-深度卷积生成对抗网络-转置卷积
- C# KeyUp事件中MessageBox的回车(Enter)键问题
- OpenHarmony代码下载编译流程记录
- aardio 模拟键盘按键,实现msgbox对话框自动关闭
- DSF data model
- PySpark之DataFrame的常用函数(创建、查询、修改、转换)
- 数据分析系统的设计与实现