文章目录

  • 对象扩展运算符
    • 解构赋值
    • 扩展运算符
  • 数组扩展运算符
    • 替代函数的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——扩展运算符的作用以及使用场景相关推荐

  1. Javascript中扩展运算符的作用及使用场景

    扩展运算符(-)是ES6新增的一个运算符,下面来介绍一下它的使用场景及作用 1.作为函数的形参 在作为函数的形参时,通过 -数组名 来表示,也称为rest参数,当函数被调用时传入的实参全部会被放入到这 ...

  2. 扩展运算符的作用及使用场景

    (1)对象扩展运算符 对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. let bar = { a: 1, b: 2 }; let baz = { ...bar }; ...

  3. 扩展运算符的作用以及使用场景

    扩展运算符 对象的扩展运算符(-)用于取出参数对象中的所有可遍历属性,拷贝到当前对象中 在JS中,扩展运算符(spread)是三个点-,剩余运算符(rest)同为三个点(-) 我们可以把-理解为展开运 ...

  4. ES6扩展运算符的几个小技巧

    es6扩展运算符,也就是...  ,作用是将一个数组转为用逗号分隔的参数序列. 那么问题来了,我们为啥要用这么奇怪的东东涅,当然因为它能大大提高我们的开发效率.所以,可别小看这三个点. 1.复制数组( ...

  5. ES6 - 扩展运算符与Object.assign对象拷贝与合并

    文章目录 扩展运算符能做什么? 什么是深浅拷贝 使用ES6扩展运算符 对象浅拷贝 对象合并 Object.assign Object.assign详解 Object.assign()实用 给对象添加属 ...

  6. ES6——扩展运算符(...)

    文章目录 一.扩展运算符(...) 二.数组扩展运算符的应用 1.合并数组 2.与解构赋值结合 3.字符串转数组 4.实现了 Iterator 接口的对象 5.Map 和 Set 结构, Genera ...

  7. php es6写法,ES6...扩展运算符(示例代码)

    在数组中的应用 扩展运算符(spread)是三个点(...).它好比 rest 参数的逆运算,++将一个数组转为用逗号分隔的参数序列++. console.log(...[1, 2, 3]) // 1 ...

  8. js数组合并、去重、降维(ES6:扩展运算符、Set)

    js数组合并.去重.降维 1.合并 1.1使用concat()进行合并数组 function get_concat(collection_a, collection_b) {return collec ...

  9. JS之ES6扩展运算符三个点(...)用于剥离{} 获取属性值

    1.用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中 let bar = { a: 1, b: 2 }; let baz = { ...bar }; // { a: 1, b: 2 } 等价于 ...

最新文章

  1. Windows客户端C/C++编程规范“建议”——结构
  2. Django常用模板标签
  3. 【转】eclipse android 设置及修改生成apk的签名文件 -- custom debug keystore
  4. 工业界推荐系统实用分析技巧
  5. REST framework 权限管理源码分析
  6. matlab gui实例_App Designer 自学实例8
  7. Python里面search()和match()的区别?
  8. 力扣104. 二叉树的最大深度(JavaScript)
  9. makefile高级应用
  10. 微软私有云分享(R2)21 BMC提升B格
  11. [日常] 最近的一些破事w...
  12. AcWing 905. 区间选点(贪心)
  13. win7程序员御用主题包制作
  14. DCGAN-深度卷积生成对抗网络-转置卷积
  15. C# KeyUp事件中MessageBox的回车(Enter)键问题
  16. OpenHarmony代码下载编译流程记录
  17. aardio 模拟键盘按键,实现msgbox对话框自动关闭
  18. DSF data model
  19. PySpark之DataFrame的常用函数(创建、查询、修改、转换)
  20. 数据分析系统的设计与实现

热门文章

  1. Ios还是安卓的判断
  2. xmuoj | 放苹果
  3. 一颗眼睛是怎么称霸整个安全界的?一文带你详解nmap安全利器,附赠全程实操视频!
  4. 汽车网络安全之——CAN安全分析方法总结(国外文献)
  5. arcgis10常见问题
  6. 清科-2018中国PE投资机构排名
  7. 企业软文营销如何利用创造性思维开拓市场
  8. pycharm光标变粗—pycharm光标设置
  9. 阴阳屏再现,iPhone12屏幕出现问题,苹果似乎被LG坑了
  10. 首次成功的web渗透