对象是一组数据和功能的集合,在我们日常编程中必不可缺,ES6 在对象方面做了很多改进,一起来看下。

一、对象字面量扩展

1.1、对象属性简写

要定义一个对象,为了简单方便起见,通常会使用对象字面量的形式,如下:

var name = 'yix',
age = 28;

var person = {
name: name,
age: age
};

console.log(person); // {name: "yix", age: 28}

都是相同名称,或许,你会觉得在 person 对象里要写两遍 nameage 显得比较繁琐。针对这个问题,ES6提供了一个简写方式:

var name = 'yix',
age = 28;

var person = {
name,
age
};

console.log(person); // {name: "yix", age: 28}

如上面代码所看到的,所谓的简写方式主要是通过去除对象属性后面的 冒号,以及相同名称的属性值。

所以,在 ES6 中,以下两种写法也是等价的:

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

// 前后写法等价

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

console.log(person('yix', 28)); // {name: "yix", age: 28}

1.2 函数简写定义

类似地,除了对象属性简写外,ES6 还引入了函数定义的简写。如,下面两种写法是等价的:

var person = {
name: 'yix',
showName: function() {
console.log(this.name);
}
};

// 前后写法等价

var person = {
name: 'yix',
showName() {
console.log(this.name);
}
};

person.showName(); // 'yix'

通过对比,我们可以发现,在对象中函数简写定义就是去除 冒号,以及关键词 function 。这样一来,整体代码变小,也提高了编码效率。

1.3 获取属性名

对象字面量是一组键值对的组合。一般来说,要设置或获取对象中的属性值,主要有两种方式,通过 方括号 [] 或者使用 点号 .。如下面:

var person = {};

person.name = 'yix';
person['age'] = 28;

console.log(person['name'], person['age']); // 'yix' 28
console.log(person.name, person.age); // 'yix' 28

这两种获取属性值的方式各有优缺点,点号 . 方式书写更简洁,但对于一些特殊的属性名,比如,当属性名中包含空格时,它无法正常获取属性值,甚至会抛出一个错误:

var person = {
'my name': 'yix'
};

console.log(person['my name']); // 'yix'
console.log(person.my name); // 报错 missing ) after argument list

由上面代码,方括号 [] 这种形式则不会受含有空格属性名的限制,但它的写法相对于 点号 . 来说又繁琐了些。

在 ES6 中,我们可以使用变量来定义对象中的属性,同时,也可以使用该变量(或等价的值)来获取对象下对应的属性值:

var myName = 'my name';

var person = {
[myName]: 'yix'
};

console.log(person[myName], person['my name']); // 'yix' 'yix'

这样的好处是,我们可以同时在对象内部或外部(其他函数或对象)使用这个变量。

其实,不仅仅如此。ES6 还允许我们通过 表达式 的方式来定义属性:

var name = ' name';

var person = {
['my' + name]: 'yix',
['you' + name]: 'guest',
['show' + name]() {
console.log(this['my name'], this['you name']);
}
};

person['show name'](); // 'yix' 'guest'

二、对象方法扩展

2.1 Object.is()

要比较两个数据是否相等,我们通常会使用 =====。因为使用 == 作比较时,右边的数据会先作类型转换再进行比较,这样就会经常出现一些不同类型的数据却相等的情况。所以,在平常的开发中,我们更倾向于使用 ===,因为它是直接比较,只有完全相等的两个数据才会返回 true

ES6 中在对象上新增了 Object.is() 方法,我们用它来判断两个数据是否完全相等,它的作用和 === 基本相同,但也有略微差异:

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

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

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

其实,在我们看来,+0 和 0 的确是不等,但 === 却返回了 true,而 Object.is(+0, -0) 则是返回 false

ES5中规定,NaN 与任何值(甚至自身)都不相等,所以 === 返回了 false 。但实际上,我们会觉得这种设计是存在缺陷的,为什么 NaN 不等于 NaN ?所以, Object.is(NaN, NaN) 返回的结果是 true

而对于一些正常数据的比较,基本和 === 返回的值是相同的。

2.2 Object.assign()

在我们平时编码中,会经常执行合并对象的操作。如果你是用 jquery库,你可以使用它现成的 API,即 $.extend(target, sources1, sources2)。但如果你是用原生js,那你只能先循环读取一个或多个源对象,再循环读取每个源对象下的属性,并把这些属性赋值到目标对象上,以下是简单实现:

if (typeof Object.assign != 'function') {
Object.assign = function(target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}

target = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}

在 ES6 中,Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。 它的语法如下,其中 target 为目标对象,而 sources 为任意多个源对象:

Object.assign(target, ...sources)

来看它的一些实际例子,首先是合并多个对象:

var target = {a: 1},
sources1 = {b: 2},
sources2 = {c: 3};

console.log(Object.assign(target, sources1, sources2)); // {a: 1, b: 2, c: 3}

当源对象中和目标对象之间、或者源对象和源对象之间,有属性名是相同的,则后面对象的属性值会覆盖前面的:

var target = {a: 1},
sources1 = {a:2, b: 2},
sources2 = {b: 3};

console.log(Object.assign(target, sources1, sources2)); // {a: 2, b: 3}

属性值是基本类型还好,但倘若属性值为复杂类型,则通常会出现一些值得丢失,因为会被覆盖:

var obj1 = {people: {name: 'yix', age: 28}},
obj2 = {people: {job: 'web'}};

console.log(Object.assign(obj1, obj2)); // {people: {job: 'web'}}

如果该方法的参数是原始类型,则它会把该参数转换为对象,并且需要注意的是,只有字符串类型才能被枚举出来,而数字类型、布尔类型只会转换为对应的包装对象:

var v1 = 'abc';
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');

var obj1 = Object.assign(v1);
var obj2 = Object.assign(v2);
var obj3 = Object.assign(v3);
var obj4 = Object.assign({}, v1, null, v2, undefined, v3, v4);

console.log(obj1); // {0: 'a', 1: 'b', 2: 'c', length: 3, [[PrimitiveValue]]: 'abc'}
console.log(obj2); // {[[PrimitiveValue]]: true}
console.log(obj3); // {[[PrimitiveValue]]: 10}
console.log(obj4); // { '0': 'a', '1': 'b', '2': 'c' }

如果该方法的参数是数组,它同样会把数组当成对象来处理。比如 [‘a’, ‘b’] 会处理成 {0: ‘a’, 1: ‘b’},所以如果合并的两个数组的话,则后面的数组项会替换前面的数组项,因为它们具有相同的属性名:

var obj = Object.assign(['a', 'b', 'c'], ['aa', 'bb'], ['e']);

console.log(obj); // ["e", "bb", "c"]

这显然是不可取的。上面的做法只是一个演示,如果要合并多个数组,我们可以用数组自身的方法 concat

如果该方法的参数是一些特殊值,比如 undefinednull,那它会直接报错,因为这些特殊值无法转换为对象:

console.log(Object.assign(undefined)); // 报错 Cannot convert undefined or null to object
console.log(Object.assign(null)); // 报错 Cannot convert undefined or null to object

前面的定义中说了,Object.assign 只是浅拷贝,它只能拷贝 源对象自身的并且可枚举的属性,而 继承属性和不可枚举属性是不能拷贝的。

var person = function(name) {
this.name = name;
};

person.prototype.age = 28;

var person1 = new person('yix');

console.log(person1); // {name: "yix"}
console.log(person1.age); // 28

console.log(Object.assign({}, person1)); // {name: "yix"}

上面中的 age 属性是对象原型上的,这是属于原型继承属性,因此不能被拷贝到源对象上。

三、原型方法扩展

一直以来,原型(prototype)都是JS中面向对象编程最重要的一部分。通过它,我们可以在对象之间继承各种属性和方法。

3.1 Object.getPrototypeOf()

该方法最早出现在 ES5 中,它的作用是获取对象的prototype:

var person = {
name: 'yix'
};

var person1 = Object.create(person);

console.log(Object.getPrototypeOf(person1)); // {name: "yix"}
console.log(Object.getPrototypeOf(person1) === person); // true

3.2 Object.setPrototypeOf()

虽然通过上面的方法很轻松的就获取到对象的prototype,但却没有一种符合规范的方法可以改变对象的prototype(实际上可以用 __proto__ 进行 Polyfill),也就是说只能读取,不能设置。而 ES6 提供了 Object.setPrototypeOf 方法解决了这个问题,该方法的语法如下,其中 obj 为被设置原型的对象,而 prototype 为设置的原型:

Object.setPrototypeOf(obj, prototype)

来看个实际例子:

var person1 = {
name: 'yix'
};

var person2 = {
name: 'abc'
};

var newPerson = Object.create(person1);

console.log(newPerson.name); // 'yix'
console.log(Object.getPrototypeOf(newPerson) === person1); // true

// 改变 prototype
Object.setPrototypeOf(newPerson, person2);

console.log(Object.getPrototypeOf(newPerson) === person1); // false
console.log(newPerson.name); // 'abc'
console.log(Object.getPrototypeOf(newPerson) === person2); // true

上面的例子中,newPerson 一开始继承的是 person1 对象,所以对应的属性和原型都是 person1 对象。而后面通过 setPrototypeOf 改变了 newPerson 的原型,于是它所有的内容都来自 person2 对象。其实读取和设置原型,都是基于js内部的 [[Prototype]] 对象。

3.3 super

由于对象中存在多层继承,有时候可能无法找到对象的原型对象。而 ES6 中引入 super ,它是一个指向当前对象原型的指针,这样,我们可以很轻松的访问原型对象。

还是用例子来说明下:

var person1 = {
showName: function() {
return 'yix';
}
};

var person2 = {
showName: function() {
return Object.getPrototypeOf(this).showName() + ' abc';
}
};

Object.setPrototypeOf(person2, person1);
console.log(person2.showName()); // 'yix abc'

var person3 = Object.create(person2);
var person4 = Object.create(person3);

console.log(person3.showName()); // 'yix abc abc'
console.log(person4.showName()); // 'yix abc abc abc'

上面的代码中,对象 person3superperson2,而 person4superperson3