对象类别

· 普通(Ordinary)对象 具有JavaScript对象所有的默认内部行为。
· 特异(Exotic)对象 具有某些与默认行为不符的内部行为。
· 标准(Standard)对象 ECMAScript 6规范中定义的对象,例如,Array、Date等。标准对象既可以是普通对象,也可以是特异对象。
· 内建对象 脚本开始执行时存在于JavaScript执行环境中的对象,所有标准对象都是内建对象。在整本书中,我们将使用这些术语来解释ECMAScript 6定义的各种对象。

对象字面量语法扩展

属性初始值的简写

es5中

  1. function createPerson(name,age) {
  2. return {
  3. name:name,
  4. age:age
  5. };
  6. }

name和age分别重复了两遍,只是其中一个是对象属性名称,另一个是为属性赋值的变量

es6中简化写法
如果对象的属性与本地变量同名时,不用再写冒号和值,简单地只写属性名即可。

  1. function createPerson(name,age) {
  2. return {
  3. name,
  4. age
  5. };
  6. }

当对象字面量里只有一个属性的名称时,js引擎会在可访问作用域中查找同名的变量

例如

  1. const foo = 'bar';
  2. const baz = {foo};
  3. baz // {foo: "bar"}
  4. // 等同于
  5. const baz = {foo: foo};
  1. function f(x, y) {
  2. return {x, y};
  3. }
  4. // 等同于
  5. function f(x, y) {
  6. return {x: x, y: y};
  7. }
  8. f(1, 2) // Object {x: 1, y: 2}

这种写法用于函数的返回值,将会非常方便。

  1. function getPoint() {
  2. const x = 1;
  3. const y = 10;
  4. return {x, y};
  5. }
  6. getPoint()
  7. // {x:1, y:10}

对象方法的简写语法

es5

  1. var person = {
  2. name: "abc",
  3. sayName: function () {
  4. console.log(this.name);
  5. }
  6. }

es6
去掉冒号与function关键字

  1. var person = {
  2. name: "abc",
  3. sayName() {
  4. console.log(this.name);
  5. }
  6. }
  7. console.log(person.sayName.name); // sayName

通过对象方法简写语法创建的方法有一个name属性,其值为小括号前的名称,在上述示例中,person.sayName()方法的name属性的值为”sayName”。

可计算属性名

在ECMAScript 5及早期版本的对象实例中,如果想要通过计算得到属性名,就需要用方括号代替点记法。有些包括某些字符的字符串字面量作为标识符会出错,其和变量放在方括号中都是被允许的。请看这个示例:
变量lastName 等于 “last name”

  1. var person = {},
  2. lastName = "last name";
  3. person["first name"] = "Nicholas";
  4. person[lastName] = "Zakas";
  5. console.log(person["first name"]); //Nicholas
  6. console.log(person[lastName]); // Zakas
  7. console.log(person["last name"]); // Zakas

变量lastName被赋值为字符串”last name”,引用的两个属性名称中都含有空格,因而不可使用点记法引用这些属性,却可以使用方括号,因为它支持通过任何字符串值作为名称访问属性的值。

  1. var person = {
  2. "first name": "Nicholas"
  3. }
  4. console.log(person["first name"]);

这种模式适用于属性名提前已知或可被字符串字面量表示的情况。然而,如果属性名称”first name”被包含在一个变量中(就像之前示例中的那样),或者需要通过计算才能得到该变量的值,那么在ECMAScript 5中是无法为一个对象字面量定义该属性的

而在ECMAScript 6中,可在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同,也是使用方括号。(可以在对象里定义变量作为属性名)

  1. let lastName = "last name";
  2. let person = {
  3. "first name": "Nicholas",
  4. [lastName]: "Zakas"
  5. }
  6. console.log(person["first name"]); //Zakas
  7. console.log(person[lastName]); //Zakas

在对象字面量中使用方括号表示的该属性名称是可计算的,它的内容将被求值并被最终转化为一个字符串,因而同样可以使用表达式作为属性的可计算名称,例如:

  1. var suffix = " name";
  2. var person = {
  3. ["first" + suffix]: "Nicholas",
  4. ["last" + suffix]: "Zakas"
  5. }
  6. console.log(person["first name"]) //"Nicholas"
  7. console.log(person["last name"]) //"Zakas"

新增方法

object.is()

大部分情况与===运算符相同 唯一的区别在于 +0 和 -0 被识别为不相等并且NaN 与 NaN等价

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

        console.log(NaN == NaN);
        console.log(NaN === NaN);
        console.log(Object.is(NaN, NaN));

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

object.assign

Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign()方法可以接受任意数量的源对象,并按指定的顺序将属性复制到接收对象中。所以如果多个源对象具有同名属性,则排位靠后的源对象会覆盖排位靠前的

        var receiver = {};

        Object.assign(receiver, {
            type: "js",
            name: "file.js"
        },
            {
                type: "css"
            }
        );
        console.log(receiver.type); // "css"
        console.log(receiver.name) // "file.js"

重复的对象字面量属性

es5严格模式中同时存在多个同名属性时会抛出错误

"use strict";

var person = {
            name:"Nicholas",
          name:"Greg" //报错
}

es6中选取最后一个取值

var person = {
            name:"Nicholas",
          name:"Greg" //报错
}
console.log(person.name); //"Greg"

自有属性枚举顺序

基本规则:
1.所有数字键按升序排序。

2.所有字符串键按照它们被加入对象的顺序排序。
3.所有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

注:Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名

增强对象的原型

改变对象的原型 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()); // dog
        console.log(Object.getPrototypeOf(friend) === dog); //true

简化原型访问的super引用

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

        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) === person);//true

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

super简写 结果与上方一致

        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) === person);//true


注:必须要在使用简写方法的对象中使用Super引用**

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

super在多重继承的情况下非常有用

es5中多重继承引发的错误**

        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!

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


使用 super**
super始终指向正确的对象
Super引用不是动态变化的,它总是指向正确的对象,在这个示例中,无论有多少其他方法继承了getGreeting方法,super.getGreeting()始终指向person.get-Greeting()方法。

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

        // 以person对象为原型
        let friend = {
            getGreeting: function () {
                return super.getGreeting() + ",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!

正确的方法定义

在ECMAScript 6以前从未正式定义“方法”的概念,方法仅仅是一个具有功能而非数据的对象属性。而在ECMAScript 6中正式将方法定义为一个函数,它会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象。请思考以下这段代码:

        let person = {
            // 是方法
            getGreeting() {
                return "Hello";
            }
        };

        // 不是方法
        function shareGreeting() {
            return "Hi";
        }

这个示例中定义了person对象,它有一个getGreeting()方法,由于直接把函数赋值给了person对象,因而getGreeting()方法的[[HomeObject]]属性值为person。而创建shareGreeting()函数时,由于未将其赋值给一个对象,因而该方法没有明确定义[[HomeObject]]属性。在大多数情况下这点小差别无关紧要,但是当使用Super引用时就变得非常重要了。Super的所有引用都通过[[HomeObject]]属性来确定后续的运行过程。第一步是在[[HomeObject]]属性上调用Object.getPrototypeOf()方法来检索原型的引用;然后搜寻原型找到同名函数;最后,设置this绑定并且调用相应的方法。请看下面这个示例:

调用friend.getGreeting()方法会将person.getGreeting()的返回值与”, hi!”拼接成新的字符串并返回。friend.getGreeting()方法的[[HomeObject]]属性值是friend,friend的原型是person,所以super.getGreeting()等价于person.getGreeting.call(this)。

小结

对象是JavaScript编程的核心,ECMAScript 6为对象提供了许多简单易用且更加灵活的新特性。
ECMAScript 6在对象字面量的基础上做出了以下几个变更:简化属性定义语法,使将当前作用域中的同名变量赋值给对象的语法变得更加简洁;添加可计算属性名特性,允许为对象指定非字面量属性名称;添加对象方法的简写语法,在对象字面量中定义方法时可以省略冒号和function关键字; ECMAScript 6弱化了严格模式下对象字面量重复属性名称的校验,即使在同一个对象字面量中定义两个同名属性也不会抛出错误。
Object.assign()方法可以一次性更改对象中的多个属性,如果使用混入(Mixin)模式这将非常有用;Object.is()方法对于所有值进行严格等价判断,当将其用于处理特殊JavaScript值问题时比===操作符更加安全。
在ECMAScript 6中同样清晰定义了自有属性的枚举顺序:当枚举属性时,数值键在先,字符串键在后;数值键总是按照升序排列,字符串键按照插入的顺序排列。
通过ECMAScript 6的Object.setPrototypeOf()方法,我们可以在对象被创建后修改它的原型。
最后,可以使用super关键字调用对象原型上的方法,此时的this绑定会被自动设置为当前作用域的this值。