Rest/Spread 属性_探索 ES2018 和 ES2019

时间: 2018-06-14阅读: 261标签: es6

Rest/Spread 属性

Sebastian Markbåge的ECMAScript提案『Rest/Spread属性』可以:

  • rest操作符(…)在对象解构中的使用。目前,该操作符仅适用于数组解构和参数定义。
  • spread操作符(…)在对象字面量中的使用。目前,这个操作符只能在数组字面量和函数以及方法调用中使用。


在对象解构中使用rest操作符(...)

在对象解构模式中,rest操作符(…)将解构源的所有可枚举的属性复制到其操作数中,但对象自面量中已经提及的那些属性除外。

const obj = {foo:1,bar:2,baz:3};
const {foo,...rest} = obj;
// Same as:
// const foo = 1;
// const reset = {bar: 2,baz: 3};

如果你正在使用对象解构来处理命名参数,rest操作符(…)可以收集其余所有参数。

function func(param1, param2, ...rest){ // rest 操作符
    console.log('All parameters:',
               {param1,param2,...rest}); // spread 操作符
    return param1 + param2;
}


语法限制

在每个对象字面量的顶层,最多可以使用一次rest操作符,并且必须出现在对象字面量的末尾:

const {...rest,foo} = obj; // SyntaxError
const {foo,...rest1,...rest2} = obj; // SyntaxError

但是,如果对象字面量是嵌套的,就可以多次使用rest操作符:

const obj = {
    foo: {
        a:1,
        b:2,
        c:3,
    },
    bar: 4,
    baz: 5,
};
const {foo:{a,...rest1},...rest2} = obj;
// Same as:
// const a = 1;
// const rest1 = {b:2,c:3};
// const rest2 = {bar:4,baz:5};

###在对象字面量中使用spread操作符(...)

通过对象字面量创建对象时,spread操作符(…)将其操作数的所有可枚举属性插入到创建的对象中:

> const obj = {foo:1,bar:2,baz:3};
> {...obj,qux:4}
{foo:1,bar:2,baz:3,qux:4}

请注意,即使属性不冲突,顺序也很重要,因为对象会记录插入的顺序:

> {qux:4,...obj}
{qux:4,foo:1,bar:2,baz:3}

如果属性发生冲突,顺序排在后面的属性值会覆盖前面的属性值:

> const obj = {foo:1,bar:2,baz:3};
> {...obj,foo:true}
{foo:true,bar:2,baz:3}
> {foo:true,...obj}
{foo:1,bar:2,baz:3}


spread操作符的常见用例

在本节中,我们将介绍spread操作符可以在哪些场景中使用。在这些场景中我们还会用到Object.assign()方法,这个方法和spread操作符类似(我们将在后面详细介绍)。

克隆对象

克隆对象Obj的可枚举属性:

const clone1 = {...obj};
const clone2 = Object.assign({},...obj);

克隆对象的原型总是Object.prototype,通过对象字面量创建的对象的原型默认也是Object.prototype:

> Object.getPrototypeOf(clone1) === Object.prototype
true
> Object.getPrototypeOf(clone2) === Object.prototype
true
> Object.getPrototypeOf({}) === Object.prototype
true

克隆一个对象Obj,包括它的原型:

const clone1 = {__proto__: Object.getPrototypeOf(obj),...obj};
const clone2 = Object.assign(
    Object.create(Object.getPrototypeOf(obj)),obj
);

请注意,对象字面量中的Proto只是Web浏览器中实现的属性,一般来说,在javascript引擎中没有实现。(译者注:当Object.prototype.__proto__ 已被大多数浏览器厂商所支持的今天,其存在和确切行为仅在ECMAScript 2015规范中被标准化为传统功能,以确保Web浏览器的兼容性。为了更好的支持,建议只使用 Object.getPrototypeOf()


真正的克隆对象

有时我们需要忠实地复制一个对象Obj的所有属性,包括(writable,enumerable,…)getter和setter。这时Object.assign()和spread操作符就不在起作用,我们需要使用属性描述符

const clone1 = Object.defineProperties({},
   Object.getOwnPropertyDescriptors(obj)                                    
);

Object.getOwnPropertyDescriptors()在『探索ES2016和ES2017』中有解释。


陷阱:克隆总是浅拷贝

请记住,通过之前讲过几种克隆方法,我们只能得到浅拷贝:如果其中的一个原始属性值是对象,则克隆将引用同一对象,但不会(递归地,深入地)克隆自己:

const original = {prop:{}};
const clone = Object.assign({},original);

console.log(original.prop === clone.prop);// true
original.prop.foo = 'abc';
console.log(clone.prop.foo); // abc


各种其他用例

合并两个对象obj1和obj2:

const merged = {...obj1,...obj2};
const merged = Object.assign({},obj1,obj2);

填写用户数据的默认值:

const DEFAULTS = {foo:'a',bar:'b'};
const userData = {foo:1};

const data = {...DEFAULTS,...userData};
const data = Object.assigin({},DEFAULTS,userData);
// {foo:1,bar:'b'}

非破坏性地更新foo属性:

const obj = {foo: 'a', bar: 'b'};
const obj2 = {...obj, foo: 1};
const obj2 = Object.assign({}, obj, {foo: 1});
// {foo: 1, bar: 'b'}

为内联属性foo和bar指定默认值:

const userData = {foo:1};
const data = {foo:'a',bar:'b',...userData};
const data = Object.assign({},{foo:'a',bar:'b'},userData);
// {foo:1,bar:'b}


Spread与Object.assign()

spread操作符和Object.assign()非常相似,两者的主要区别是spread定义新的属性,但Object.assign()设置它们。我们会在后面解释到底是什么意思。

使用Object.assign()的两种方式

使用Object.assign()这里有两种方式:

第一种方式:破坏性地(现有的对象会被改变)。

Object.assign(target, source1, source2);

上面的代码中,target会被改变;source1和source2被复制到target中。

第二种方式:非破坏性地(现有的对象不会被改变)。

const result = Object.assign({}, source1, source2);

上面的代码中,通过对象字面量创建了一个空对象,并且source1和source2被复制到其中。

spread操作符与使用Object.assign()的第二种方式非常相似。接下来,我们就来看看两者的相似之处以及它们的不同之处。

spread和Object.assign()都是通过"get "取值

两个操作都是通过「get」从源对象读取属性,然后再把取到的属性写入目标对象。结果,在这个过程中,getters变成了普通的数据属性。

下面来看个例子:

const original = {
    get foo() {
        return 123;
    }
};

original的getter为foo(它的属性描述符有get和set属性)

> Object.getOwnPropertyDescriptor(original, 'foo')
{ get: [Function: foo],
  set: undefined,
  enumerable: true,
  configurable: true }

但它的克隆clone1和clone2,foo是一个普通的数据属性(它的属性描述符具有属性值并且是可写的)

> const clone1 = {...original};
> Object.getOwnPropertyDescriptor(clone1, 'foo')
{ value: 123,
  writable: true,
  enumerable: true,
  configurable: true }

> const clone2 = Object.assign({}, original);
> Object.getOwnPropertyDescriptor(clone2, 'foo')
{ value: 123,
  writable: true,
  enumerable: true,
  configurable: true }


Spread定义属性,Object.assign()设置属性

spread操作符在目标对象中定义新属性,Object.assign()通过『set』来创建属性,这有两个后果。

使用setter的目标对象

首先,Object.assign()会触发setters,但spread不会触发:

Object.defineProperty(Object.prototype, 'foo', {
    set(value) {
        console.log('SET', value);
    },
});
const obj = {foo: 123};

上面这段代码插入了一个能被所有普通对象继承的setter foo。

如果我们通过Object.assign()克隆obj,则会触发这个继承的setter:

> Object.assign({}, obj)
SET 123
{}

使用spread操作符,则不会:

> { ...obj }
{ foo: 123 }

Object.assign()也会在复制期间触发自己的setter,它不会覆盖它们。

具有只读属性的目标对象

另外,通过继承只读属性Object.assign()可以停止创建自己的属性,但spread操作符不能。

Object.defineProperty(Object.prototype, 'bar', {
    writable: false,
    value: 'abc',
});

上面这段代码插入了一个能被所有普通对象继承的只读属性bar。

这样的话,就不能再通过赋值来创建自己的属性bar(只会在严格模式下得到一个异常;在非严格模式下,设置失败不会有异常提示)

> const tmp = {};
> tmp.bar = 123;
TypeError: Cannot assign to read only property 'bar'

在下面的代码中,我们通过字面量成功创建了属性bar。这是有效的,因为字面量不设置属性,它们定义属性:

const obj = {bar: 123};

但是,Object.assgin()通过赋值来创建属性,这就是为什么我们无法克隆obj的原因:

> Object.assign({}, obj)
TypeError: Cannot assign to read only property 'bar'

使用spread操作符是可以克隆的:

> { ...obj }
{ bar: 123 }


spread和Object.assign()都只考虑自己的枚举属性

两个操作都忽略所有继承的属性和所有不可枚举的属性。

下面的obj对象继承了proto中的一个(可枚举)属性,并且有两个自己的属性:

const proto = {
    inheritedEnumerable: 1,
};
const obj = Object.create(proto, {
    ownEnumerable: {
        value: 2,
        enumerable: true,
    },
    ownNonEnumerable: {
        value: 3,
        enumerable: false,
    },
});

如果你克隆obj,结果只有属性ownEnumerable。不会复制inheritedEnumerable和ownNonEnumerable属性:

> {...obj}
{ ownEnumerable: 2 }
> Object.assign({}, obj)
{ ownEnumerable: 2 }


原文链接: exploringjs.com 
 翻译来源:www.zcfy.cc

es6 Reflect对象

Reflect是ES6为操作对象而提供的新API,而这个API设计的目的只要有:将Object对象的一些属于语言内部的方法放到Reflect对象上,从Reflect上能拿到语言内部的方法。如:Object.defineProperty,修改某些object方法返回的结果。让Object的操作都变成函数行为。

ES6 系列之私有变量的实现

在阅读 《ECMAScript 6 入门》的时候,零散的看到有私有变量的实现。旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容

es6中const定义的属性是否可以改变_为什么有人说const并非一定为常量

const是用来定义常量的,而且定义的时候必须初始化,且定义后不可以修改。const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

ES5和ES6对象导出和导入

如果模块中使用 export default {},只能通过 import 对象名称 from 模块路径 ,不能通过 import {对象名称} from 模块路径。如果使用 import {对象名称} from 模块路径 导出具体某个对象或者方法名称

在使用es6语法class的时候,babel到底做了什么?

自从有了webpack之后,我们这些jscoder似乎得到了前所未有的解放,箭头函数,对象解构,let,const关键字,以及class、extends等等,webpack会帮我们把这些es6代码转换成浏览器能够识别的es5代码,那么有多少人真正的看过,babel转换之后的代码呢?

使用ES6让你的React代码提升到一个新档次

ES6使您的代码更具表现力和可读性。而且它与React完美配合!现在您已了解更多基础知识:现在是时候将你的ES6技能提升到一个新的水平!嵌套props解构、 传下所有props、props解构、作为参数的函数、列表解构

ES6 中的一些技巧,使你的代码更清晰,更简短,更易读!

ES6 中的一些技巧:模版字符串、块级作用域、Let、Const、块级作用域函数问题、扩展运算符、函数默认参数、解构、对象字面量和简明参数、动态属性名称、箭头函数、for … of 循环、数字字面量。

基于ES6的tinyJquery

Query作为曾经Web前端的必备利器,随着MVVM框架的兴起,如今已稍显没落。用ES6写了一个基于class简化版的jQuery,包含基础DOM操作,支持链式操作...

ES6 Decorator_js中的装饰器函数

ES6装饰器(Decorator)是一个函数,用来修改类的行为 在设计阶段可以对类和属性进行注释和修改。从本质上上讲,装饰器的最大作用是修改预定义好的逻辑,或者给各种结构添加一些元数据。

ES6的7个实用技巧

ES6的7个实用技巧包括:1交换元素,2 调试,3 单条语句,4 数组拼接,5 制作副本,6 命名参数,7 Async/Await结合数组解构

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告合作文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯