深入理解 ES6-ch4 扩展对象的功能性

  1. 可计算属性名
  2. 新增方法
    1. Object.is()
    2. Object.assign
    3. 字面量对象的重复属性
    4. 自有属性的枚举顺序
    5. 增强的对象原型
    6. 用来简化原型访问的 Super

可计算属性名

现在,属性可以在字面量中直接写

let p = "a b c";
let a = {[p]: '123';
}

ES5 不能直接写,只能这么写:

var p = "a b c";
var a = { };
a[p] = '1 2 3';

新增方法

Object.is()

严格相等运算 === 不会触发强制类型转换,但是也有不准确的时候。

+0 === -0 // true
NaN === NaN // false

在 JS 中,-0+0 是不同的实体。应该是 false。NaN 与自身比较应该是 true。

ES6 引入了 Object.is() 弥补全局运算符的不准确。

===Object.is() 的唯一区别就是 +0,-0 ,NaN 这两种情况。

Object.is(+0, -0) //false
Object.is(NaN, NaN) // true

当你的严格相等代码中,有可能出现以上两种情况,考虑使用 Object.is

Object.assign

混合(mixin)是 JS 中对象组合的常见模式。

例如

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

mixin 函数遍历 supplier 的自有属性并复制到 receiver 中(浅复制)。使得 receiver 可以不通过继承,获得属性。

function EventTarget() {}
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function(){},
    on: function(){}
};

var myObj = {};
mixin(myObj, EventTarget.prototype);

myObj.emit("somethingChanged");

这里,myObj 接收了 EventTarget.prototype 对象的所有行为,从而可以使用 emit 发布事件,可以通过 on 订阅事件。

ES6 的 Object.assign 方法实现了上面的 mixin 的功能

Object.assign(接收对象,源对象 1,源对象 2,源对象 3。。。)

注意,assign 不能复制访问器属性(getter 和 setter),并且访问器属性的 enumerable 是 true 时才能被 assign 方法发现,如下所示:

// assign 不能复制访问器属性
let supplier = {
    age: 18,
}
Object.defineProperty(supplier, 'year', {
    get() {
        console.log('调用了 get');
        return this.age;
    },
    set(value) {
        console.log('set 被调用了');
        this.age = value;
    },
    enumerable: true,

})

supplier.year = 2022;
// 都改成了 2022
console.log(supplier.age);
console.log(supplier.year);

let myObj = {};

Object.assign(myObj, supplier);

console.log(myObj.age);
console.log(myObj.year);

myObj.year = 2021;

// age 没有变,说明 assign 没有复制访问器属性(getter 和 setter)
console.log(myObj.age);
console.log(myObj.year);

supplier 的访问器属性会变为接收器对象的一个数据属性。不再是访问器属性。

assign 可以接 N 个源对象,排名靠后的对象会覆盖前面的同名属性。

字面量对象的重复属性

ES6 字面量中重复定义的属性,不会报错,不管是严格和非严格模式。

值取最后定义的。

自有属性的枚举顺序

ES6 严格规定明确了自有属性被枚举时的返回顺序。

被影响的方法:Obejct.assign, Object.getOwnPropertyNames,Reflect.ownKeys 等等。

ES6 没有明确 for-in 循环的属性枚举顺序。Object.keys 和 JSON.stringify 与 for-in 顺序相同,因此 ES6 也没有明确。(Chrome V100 的顺序与 ES 的枚举顺序一致)

顺序规则:

  1. 数字 key 按升序排序
  2. 字符串 key 按照 ** 被加入对象的顺序 ** 排序
  3. 所有 symbol 按照 ** 被加入对象的顺序 ** 排序
let obj = {
    0: 1,
    3: 1,
    bz: 1,
    b: 1,
    bb: 1,
    az: 1,
    ab: 1,
    1: 1,
    "-2": 1,
    "-1": 1
}
obj[2] = 1;
obj[-0] = 1;
obj[-1] = 1;

console.log(Object.getOwnPropertyNames(obj).join('-'));
// Chrome 100 结果:0-1-2-3-bz-b-bb-az-ab--2--1-a
  • 对于数字,在枚举时被重新排序。
  • 字符串键在数字键后面。顺序是声明顺序和插入对象的顺序。
  • 负数只按照前面的 “-” 排序,视为字符串。
增强的对象原型

一般情况,无论是构造函数,还是 Object.create 方法创建的对象,原型都是在对象被创建时指定的。

ES5 的原则是,在实例化之后,对象原型保持不变。但是 ES5 缺少实例化后改变原型的标准方法。ES5 的 Object.getPrototypeOf() 用来返回指定对象的原型。

在 ES6,又新增了 Obejct.setPrototypeOf() 方法,用来改变指定对象的原型。

代码如下:

let person = {
    getGreeting() {
        return 'hello';
    }
}

let dog = {
    getGreeting() {
        return 'baibai';
    }
}

let friend = Object.create(person);
console.log(friend.getGreeting());  // hello
console.log(Object.getPrototypeOf(friend) === person);  //true

Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());  // baibai
console.log(Object.getPrototypeOf(friend) === dog);  //true
用来简化原型访问的 Super

在 ES5,你想重写对象实例的方法,又需要调用与它同名的原型方法,代码如下:

// 在 ES5,你想重写对象实例的方法,又需要调用与它同名的原型方法,代码如下:
let person = {
    id: '#human',
    getGreeting() {
        return this.id + 'hello';
    }
}

let dog = {
    id: 'animal',
    getGreeting() {
        return this.id + 'baibai';
    }
}

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

Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());  // #friend hello, hi!

Object.getPrototypeOf(this) 确保得到的是 this(friend)的原型 –person。后面的 call 可以确保 this 的指向的是 friend。如果不加 call,最后打印 #human hello, hi!

ES6 的 super 引用相当于指向对象原型的指针,相当于 Object.getPrototypeOf(this), 代码如下:

let friend = {
    id: '#friend',
    getGreeting() {
        return super.getGreeting() + ", hi!";
    }
}

Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());  // #friend hello, hi!

super 不能在非简写方法中使用,会报错 SyntaxError: 'super' keyword unexpected here

let friend = {
    id: '#friend',
    getGreeting: function () {
        return super.getGreeting() + ", hi!";
    }
}

多重继承时,用 ES5 的 Object.getPrototypeOf(this)会出现循环引用的问题,代码如下:

// super 对于多重继承很有用
let friend = {
    id: '#friend',
    getGreeting() {
        // ES5 的方式在多重继承时会有问题,造成循环调用
        console.log(this.id);
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
   }
}

Object.setPrototypeOf(friend, person);

// relative 原型是 friend
let relative = Object.create(friend);

console.log(relative.getGreeting());

RangeError: Maximum call stack size exceeded。

因为 relative.getGreeting() 是调用的 friendgetGreeting 方法,Object.getPrototypeOf(this) 中的 this 指向的是 relative,调用 Object.getPrototypeOf(this) 的结果返回的就是 friend 对象,此时造成 friend.getGreeting 的循环调用。

使用 super,以上问题迎刃而解,代码如下:

let friend = {
    id: '#friend',
    getGreeting() {
        // ES5 的方式在多重继承时会有问题,造成循环调用
        console.log(this.id);
        return super.getGreeting() + ", hi!";
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间