深入理解ES6:4.扩展对象的功能性

Tips:简化属性语法、可计算属性名、简化对象方法语法、重复属性名校验、Object.is()Object.assign()、自有属性枚举、Object.setPrototypeOf()super

对象字面量语法扩展

属性初始值的简写

// createPerson() 函数创建了一个对象,对象的属性名称与函数的参数名称相同
function createPerson(name, age) {
  return {
    name: name,
    age: age
  };
}

// 在 ES6 中,当一个对象的属性与本地变量同名时,不必再写冒号和值,简单地只写属性名即可
function createPerson(name, age) {
  return {
    name,
    age
  };
}

对象方法的简写

// 为对象添加方法时,需要指定名称并完整定义函数来实现
var perosn = {
  name: "Andy",
  sayName: function () {
    console.log(this.name);
  }
}

// 在 ES6 中,消除了冒号和 function 关键字
// 在 person 对象中创建了一个 sayName() 方法
var perosn = {
  name: "Andy",
  sayName() {
    console.log(this.name);
  }
}

可计算属性名

// 如果想要通过计算得到属性名,需要用方括号代替点语法
var person = {
  // 在对象字面量中,可以直接使用字符串字面量作为属性名称
  'first name': 'Nicholas'
};

// 属性名称中带有空格时,需要使用方括号
person['first name'] = 'Andy';
var lastName = 'last name';
person[lastName] = 'Zakas';

console.log(person['first name']); // Andy
console.log(person[lastName]); // Zakas

// 在 ES6 中,可以在对象字面量中使用可计算属性名
// 示例一:
let lastName = 'last name';

let person = {
  'first name': 'Nicholas',
  [lastName]: 'Zakas'
};

console.log(person['first name']); // Nicholas
console.log(person[lastName]); // Zakas

// 示例二:
var suffix = ' name';

// 使用 [] 表示的属性名称是可计算的
let person = {
  ['first' + suffix]: 'Nicholas',
  ['last' + suffix]: 'Zakas'
};

console.log(person['first name']); // Nicholas
console.log(person[lastName]); // Zakas

新增方法

Object.is() 方法

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(Object.is(5, 5)); // true

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

Object.assign() 方法

混合(Mixin)是 JavaScript 中实现对象组合的流行方式。

Object.assign() 方法原生实现了很多第三方库的混合(Mixin)方法。

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

// mixin() 函数遍历 supplier 的自有属性并复制到 receiver 中
// 结果:receiver 不通过继承就可以获得新属性
function mixin(receiver, supplier) {
  Object.keys(supplier).forEach(function(key) {
    receiver[key] = supplier[key];
  });
  return receiver;
}

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

/*
  通过调用 mixin() 方法,
  myObject 可以接收 EventTarget.prototype 对象的所有行为,
  从而使 myObject 可以分别通过 emit() 方法发布事件或通过 on() 方法订阅事件。
*/
var myObject = {};
mixin(myObject, EventTarget.prototype);

myObject.emit('somethingChanged');

在 ES6 中,通过添加 Object.assign() 方法实现了相同的功能

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

// 在 ES6 中,通过添加 Object.assign() 方法实现了相同的功能
var myObject = {};
Object.assign(myObject, EventTarget.prototype);

myObject.emit('somethingChanged');

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

var receiver = {};

Object.assign(
  receiver,
  {
    // 源对象 1
    type: 'js',
    name: 'file.js'
  },
  { 
    // 源对象 2,后者同名属性会覆盖前者
    type: 'css'
  }
);

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

重复的对象字面量属性

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

在 ECMAScript 6 中,重复属性检查被移除了,无论是在严格模式还是非严格模式下,代码不再检查重复属性,对于每一组重复属性,都会选取最后一个取值,就像这样:

'use strict';

var person = {
  name: 'Nicholas',
  name: 'Grey' // ES5 严格模式会有语法错误,ES6 下不会。
};

console.log(person.name); // Grey

自有属性枚举顺序

ECMAScript 5 中未定义对象属性的枚举顺序,由 JavaScript 引擎厂商自行决定。

ECMAScript 6 严格规定了对象的自有属性被枚举时的返回顺序,这会影响到 Object. getownPropertyNames() 方法及 Reflect.ownKeys 返回属性的方式,Object.assign() 方法处理属性的顺序也将随之改变。

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

  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.create() 方法创建对象,其原型是在对象被创建时指定的。对象原型在实例化之后保持不变。

虽然在 ECMAScript 5 中添加了 Object.getPrototypeOf() 来返回任意对象的原型,但仍然无法在对象被实例化之后改变其原型。

ECMAScript 6 添加了 Object.setPrototypeOf() 方法,可以改变任意指定对象的原型。

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

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

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

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

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

简化原型访问的 Super 引用

ECMAScript 6 添加了 Super 引用特性,可以更便捷地访问对象原型。

必须在使用简写方法(对象方法的简写)的对象中使用 super 引用。

Super 引用在多重继承下非常有用。

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

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

/*
 friend 对象的 getGreeting() 方法调用了同名的原型方法。
 Object.getPrototypeOf() 方法可以确保调用正确的原型。
 call(this) 可以确保正确设置原型方法中的 this 值。
 */
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

ES 6 中的简化:

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

正式的方法定义

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

// person 对象有一个 getGreeting() 方法
// getGreeting() 方法的 [[HomeObject]] 属性指向 person
let person = {
  // 是方法
  getGreeting() {
    return 'Hello';
  }
};

// 不是方法
function shareGreeting() {
  return 'Hello';
}

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

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

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

// 以 Person 对象为原型
Object.setPrototypeOf(friend, person);

/*
  调用 friend.getGreeting() 方法会将 person.getGreeting() 的返回值与 ', hi!' 字符串拼接并返回。

  friend.getGreeting() 方法的 [[HomeObject]] 属性是 firend。
  friend 的原型是 person。
  所以 super.getGreeting() 等价于 person.getGreeting.call(this)。
*/
console.log(friend.getGreeting()); // Hello, hi!

你可能感兴趣的:(深入理解ES6:4.扩展对象的功能性)