ES6之扩展对象的功能性

目录

一、对象类别

二、对象字面量的语法扩展

2.1、属性初始值的简写

2.2、对象方法的简写语法

2.3、可计算属性名

三、新增方法

3.1、Object.is()方法

3.2、Object.assign()方法

四、重复的对象字面量属性

五、自有属性枚举顺序

六、增强对象原型

6.1、改变对象的原型

6.2、简化原型访问的Super引用

七、正式方法的定义


    ECMAScript6通过多种方式来加强对象的使用,通过简单的语法扩展,提供更多操作对象及与对象交互的方法。

一、对象类别

    ES6规范清晰定义了每一个类别的对象:

  • 普通对象    ——    具有JavaScript对象所有的默认内部行为
  • 特异对象    ——    具有某些与默认行为不符的内部行为
  • 标准对象    ——    ES6规范中定义的对象,例如,ArrayDate等。标准对象既可以是普通对象,也可以是特异对象
  • 内建对象    ——    脚本开始执行时存在于JavaScript执行环境中的对象,所有标准对象都是内建对象

    关系图:

二、对象字面量的语法扩展

    ES6通过下面的几种语法,让对象字面量变得更强大、更简洁。

2.1、属性初始值的简写

    在ES5及更早版本中,对象字面量中初始化属性值时会有一些重复,例如:

        function createPerson(name, age){
            return {
                name: name,
                age: age
            };
        }

    这段代码中的createPerson()函数创建了一个对象,其属性名称与函数的参数相同,在返回的结果中,nameage分别重复了两遍。

    在ES6中,通过使用属性初始化的简写语法,可以消除这种属性名称与局部变量之间的重复书写,例如:

        function createPerson(name, age){
            return {
                name,
                age
            };
        }

2.2、对象方法的简写语法

    在ES5及早期版本中,如果为对象添加方法,必须通过指定名称并完整定义函数来实现,例如:

        var person = {
            name: "Nicholas",
            sayName: function(){
                console.log(this.name);
            }
        };

    而在ES6中,语法更简洁,消除了冒号和function关键字,例如:

        var person = {
            name: "Nicholas",
            sayName(){
                console.log(this.name);
            }
        };

2.3、可计算属性名

    ES6中,可在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同,也是使用方括号。

        let lastName = "last name";
        let time = "current ";

        let person = {
            "first name": "Nicholas",
            [lastName]: "Zakas",
            [time + "age"]: 30 
        };

三、新增方法

    在ES6中,为了使某些任务更易完成,在全局Object对象上引入了一些新方法。

3.1、Object.is()方法

    ES6引入Object.is()方法来弥补全等运算符的不准确运算。

    这个方法接受两个参数,如果这两个参数类型相同且具有相同的值,则返回true

        console.log(+0 == -0);              // true
        console.log(+0 === -0);             // true
        console.log(Object.is(+0, -0));     // false

        console.log(NaN == NaN);            // false
        console.log(NaN === NaN);           // false
        console.log(Object.is(NaN, NaN));   // true

        console.log(5 == 5);                // true
        console.log(5 == "5");              // true
        console.log(5 === 5);               // true
        console.log(5 === "5");             // false
        console.log(Object.is(5, 5));       // true
        console.log(Object.is(5, "5"));     // false

    大可不必抛弃等号运算符,是否选择用Object.is()方法而不是=====取决于那些特殊情况如何影响代码。

3.2、Object.assign()方法

    混合(Mixin)是JavaScript中实现对象组合最流行的一种模式。

    在一个mixin方法中,一个对象接收来自另一个对象的属性和方法,许多JavaScript库中都有类似的mixin方法:

        function mixin(receiver, supplier){
            Object.keys(supplier).forEach(function(key){
                receiver[key] = supplier[key];
            });
            
            return receiver;
        }

     这种混合模式非常流行,因而ES6添加了Object.assign()方法来实现相同的功能。

    这个方法接受一个接收对象和任意数量的源对象,最终返回接收对象。

    示例

        var receiver = {};

        Object.assign(receiver, {
            type: "js",
            name: 'file.js'
        });

        console.log(receiver.type);     // "css"
        console.log(receiver.name);     // "file.js"

    注意:

        Object.assign()方法不能将提供者的访问器属性复制到接收对象中。

        由于Object.assign()方法执行了赋值操作,因此提供者的访问器属性最终会转变为接收对象中的一个数据属性。

    示例:

        var receiver = {},
            supplier = {
                get name(){
                    return "file.js"
                }
            };

        Object.assign(receiver, supplier);

        var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");

        console.log(descriptor.value);      // "file.js"
        console.log(descriptor.get);        // undefined

四、重复的对象字面量属性

    ES5严格模式中加入了对象字面量重复属性的校验,当同时存在多个同名属性时会抛出错误。

    示例

        "use strict";
    
        var person = {
            name: 'Nicholas',
            name: 'Greg'        // ES5严格模式下会有语法错误
        };

    但是,在ES6中重复属性检查被移除了。

    无论是在严格模式还是非严格模式下,代码不再检查重复属性,对于每一组重复属性,都会选取最后一个取值。

        "use strict";
    
        var person = {
            name: 'Nicholas',
            name: 'Greg'        // ES6严格模式下没有错误
        };
        
        console.log(person.name);       // "Greg"

五、自有属性枚举顺序

    ES6严格规定了对象的自由属性被枚举时的返回顺序。

    这会影响到Object.getOwnPropertyNames()方法及Reflect.ownKeys返回属性的方式,Object.assign()方法处理属性的顺序也将随之改变。

    自有属性枚举顺序的基本规则是:

  • 所有数字键按升序排序
  • 所有字符串键按照它们被加入对象的顺序排序
  • 所有symbol键按照它们被加入对象的顺序排序

    示例

        var obj = {
            a: 1,
            0: 1,
            c: 1,
            2: 1,
            b: 1,
            1: 1
        };

        obj.d = 1;

        console.log(Object.getOwnPropertyNames(obj).join(""));  // "012acbd"

    对于for-in循环,由于并非所有厂商都遵循相同的实现方式,因此仍未指定一个明确的枚举顺序。

    而Object.keys()方法和JSON.stringify()方法都指明与for-in使用相同的枚举顺序,因此它们的枚举顺序目前也不明晰。  

六、增强对象原型

6.1、改变对象的原型

    在ES5中,添加了Object.getPrototypeOf()方法来返回任意指定对象的原型,但仍缺少对象在实例化后改变原型的标准方法。

    所以,在ES6中添加了Object.setPrototypeOf()方法来改变任意指定对象的原型,它接受两个参数:

  • 被改变原型的对象
  • 替代第一个参数原型的对象

    示例

        let person = {
            getGreeting(){
                return "Hello";
            }
        };

        let dog = {
            getGreeting(){
                return "Woof";
            }
        };

        // 以person对象为原型
        let friend = Object.create(person);
        console.log(friend.getGreeting());      // "Hello"
        console.log(Object.getPrototypeOf(friend) === person);      // true

        // 将原型设置为dog
        Object.setPrototypeOf(friend, dog);
        console.log(friend.getGreeting());          // "Woof"
        console.log(Object.getPrototypeOf(friend) === dog);     // true

    对象原型的真实值被存储在对象实例内部专用属性[[Prototype]]中,调用Object.getPrototypeOf()方法返回储存在其中的值,调用Object.setPrototypeOf()方法改变其中的值。

6.2、简化原型访问的Super引用

    ES6引入了Super引用的特性,使用它可以更便捷地访问对象原型。

    例如,如果想重写对象实例的方法,又需要调用与它同名的原型方法,则在ES5中可以这样实现:

        let person = {
            getGreeting(){
                return "Hello";
            }
        };

        let dog = {
            getGreeting(){
                return "Woof";
            }
        };

        let friend = {
            getGreeting(){
                return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
            }
        };

        // 将原型设置为person
        Object.setPrototypeOf(friend, person);
        console.log(friend.getGreeting());          // "Hello, hi!"
        console.log(Object.getPrototypeOf(friend) === person);      // true

        // 将原型设置为dog
        Object.setPrototypeOf(friend, dog);
        console.log(friend.getGreeting());          // "Woof, hi!"
        console.log(Object.getPrototypeOf(friend) === dog);     // true

    要准确记得如何使用Object.getPrototypeOf()方法和.call(this)方法来调用原型上的方法实在有些复杂,所以ES6引入了super关键字。

    简单来说,super引用相当于指向对象原型的指针,实际上也就是Object.getPrototypeOf(this)的值。

    于是,上面的代码可以这样简化为:

        let person = {
            getGreeting(){
                return "Hello";
            }
        };

        let dog = {
            getGreeting(){
                return "Woof";
            }
        };

        let friend = {
            getGreeting(){
                // 这段代码与之前的示例中的
                // return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!" 相同
                return super.getGreeting() + ", hi!";
            }
        };

        // 将原型设置为person
        Object.setPrototypeOf(friend, person);
        console.log(friend.getGreeting());          // "Hello, hi!"
        console.log(Object.getPrototypeOf(friend) === person);      // true

        // 将原型设置为dog
        Object.setPrototypeOf(friend, dog);
        console.log(friend.getGreeting());          // "Woof, hi!"
        console.log(Object.getPrototypeOf(friend) === dog);     // true

    注意必须要在使用简写方法的对象中使用super引用,但如果在其他方法声明中使用会导致语法错误,就像这样:

        let friend = {
            getGreeting: function(){
                return super.getGreeting() + ", hi!";       // 语法错误
            }
        };

    super引用在多重继承的情况下非常有用,因为在这种情况下,使用Object.getPrototypeOf()方法将会出现问题:

        let person = {
            getGreeting(){
                return "Hello";
            }
        };

        // 以person对象为原型
        let friend = {
            getGreeting(){
                return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
            }
        };
        Object.setPrototypeOf(friend, person);

        // 原型是friend
        let relative = Object.create(friend);
        
        console.log(person.getGreeting());          // "Hello"
        console.log(friend.getGreeting());          // "Hello hi!"
        console.log(relative.getGreeting());        // error!

    relative的原型是friend对象,当执行relativegetGreeting方法时,会调用friendgetGreeting()方法,而此时的this值为relativeObject.getPrototypeOf(this)又会返回friend对象。所以就会进入递归调用直到触发栈溢出报错。

    在使用ES6中,使用super引用便可以迎刃而解:

        let person = {
            getGreeting(){
                return "Hello";
            }
        };

        // 以person对象为原型
        let friend = {
            getGreeting(){
                return super.getGreeting.call(this) + ", hi!";
            }
        };
        Object.setPrototypeOf(friend, person);

        // 原型是friend
        let relative = Object.create(friend);
        
        console.log(person.getGreeting());          // "Hello"
        console.log(friend.getGreeting());          // "Hello hi!"
        console.log(relative.getGreeting());        // "Hello hi!"

    super引用不是动态变化的,它总是指向正确的对象。

    在这个示例中,无论有多少其他方法继承了getGreeting方法,super.getGreeting()始终指向person.getGreeting()方法。

七、正式方法的定义

    ES6以前从未正式定义“方法”的概念,方法仅仅是一个具有功能而非数据的对象属性。

    而在ES6中正式将方法定义为一个函数,它会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象

        let person = {
            // 是方法
            getGreeting(){      // 内部有[[HomeObject]]属性
                return "Hello";
            }
        };

        // 不是方法
        function shareGreeting(){   // 没有[[HomeObject]]属性
            return "Hi!";
        }

    由于getGreeting()方法直接把函数赋值给了person对象,因而getGreeting()方法的[[HomeObject]]属性值为person

    而创建shareGreeting()函数时,由于未将其赋值给一个对象,因而该方法没有明确定义[[HomeObject]]属性。

    在大多数情况下这点小差别无关紧要,但是当使用super引用时就变得非常重要了。

    super的所有应用都通过[[HomeObject]]属性来确定后续的运行过程。

    第一步是在[[HomeObject]]属性上调用Object.getPrototypeOf()方法来检索原型的引用;然后搜寻原型找到同名函数;最后,设置this绑定并且调用相应的方法。

    示例

        let person = {
            // 是方法
            getGreeting(){      // 内部有[[HomeObject]]属性
                return "Hello";
            }
        };

        // 以person对象为原型
        let friend = {
            // 是方法
            getGreeting(){
                return super.getGreeting() + ", hi!";   // 内部有[[HomeObject]]属性
            }
        }
        Object.setPrototypeOf(friend, person);

        console.log(friend.getGreeting());      // "Hello, hi!"

猜你喜欢

转载自blog.csdn.net/qq_35732147/article/details/84951722