对象类别
一直以来对象的术语描述没有统一的标准,于是ES6规范定义了每一个类别的对象,对象的类别如下:
1、普通(Ordinary)对象
具有JS对象所有的默认内部行为
2、特异(Exotic)对象
具有某些与默认行为不符的内部行为
3、标准(Standard)对象
ES6规范中定义的对象,例如,Array、Date等。标准对象既可以是普通对象,也可以是特异对象
4、内建对象
脚本开始执行时存在于JS执行环境中的对象,所有标准对象都是内建对象
对象简写
属性简写
当一个对象的属性与本地变量同名时,只写属性名即可
let name = 'wmui', age = 10
let obj = {name,age}
// 等价于
let obj = {name: "wmui", age: 10}
方法简写
对象的方法也可以简写,消除了冒号和function关键字
let obj = {
name: 'wmui',
sayName() {
return this.name
}
}
// 等价于
let obj = {
name: 'wmui',
sayName: function() {
return this.name
}
}
两者的唯一区别是,简写方法可以使用super关键字,普通方法不可以
可计算属性名
在ES5中是无法为对象字面量定义可计算属性名的,比如说属性名是一个变量。但在ES6中可以通过为属性名加一对方括号来定义可计算属性名。
let suffix = ' name'
let obj = {
['first' + suffix]: 'wang',
['last' + suffix]: 'li'
}
console.log(obj['first name']) // wang
console.log(obj['last name']) // li
判断相等
在JS中,判断两个值是否相等,通常会使用相等运算符或全等运算符,但是有时候即使使用全等运算符也不一定完全靠得住
console.log(+0 === -0);//true
console.log(NaN === NaN);//false
ES6引入了Object.is()
方法来弥补全等运算符的不准确运算。这个方法接受两个参数,如果这两个参数类型相等且具有相同的值,则返回true,否则返回false
console.log(Object.is(+0, -0)) // false
console.log(Object.is(3, '3')) // false
console.log(Object.is([], [])) // false
console.log(Object.is(NaN, NaN)) // true
对象合并
ES6新增了Object.assign()方法用于对象合并,该方法的第一个参数是目标对象,后面的参数是源对象,源对象的数量不限。
Object.assign()方法可以把源对象按照顺序将属性复制到目标对象中,如果目标对象与源对象有同名属性,或者源对象与源对象有同名属性,那么后面的属性会覆盖前面的属性。
注意: 属性的复制实际上是浅拷贝的过程,并且只拷贝源对象自身的属性,对于继承属性和不可枚举属性是不拷贝的
let target = { a: 1, b: 1 };
let source1 = { b: 2, c: 2 };
let source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
let o = {};
Object.defineProperty(o, 'name', {
enumerable: false,
value: 'wmui'
})
console.log(Object.assign({a: 1},o)) // {a: 1}
let o = {a: {b: 2}};
let o2 = Object.assign({}, o)
o.a.b = 3
console.log(o2.a.b) // 3
属性名重复
在ES6中,无论是在严格模式还是非严格模式下,代码不再检查重复属性,对于每一组重复属性,都会选取最后一个取值
如果是在ES5严格模式下,当同时存在多个同名属性时会抛出错误
let o = {
name: 'li',
name: 'wang'
}
console.log(o.name) // wang
枚举顺序
ES6严格规定了对象的自有属性被枚举时的返回顺序,这会影响到Object.getOwnPropertyNames()方法及Reflect.ownKeys返回属性的方式,自有属性枚举顺序的基本规则如下:
1、所有数字键按升序排序
2、所有字符串键按照它们被加入对象的顺序排序
3、所有symbol键按照它们被加入对象的顺序排序
let obj = {
a: 1,
0: 1,
c: 1,
2: 1,
b: 1,
1: 1
};
obj.d = 1;
console.log(Object.getOwnPropertyNames(obj).join("")); // "012acbd"
对象原型
__proto__
属性,用来读取或设置当前对象的prototype对象,目前所有浏览器都部署了该属性。
标准规定,只有浏览器必须部署__proto__
属性。开发过程中最好不要直接使用该属性,而是使用Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)来代替
Object.getPrototypeOf()
Object.getPrototypeOf(obj)方法用于获取一个对象的原型对象
Object.setPrototypeOf()
Object.setPrototypeOf()方法与proto作用相同,可以改变任意指定对象的原型。该方法接收两个参数:要改变原型的对象和替代第一个参数原型的对象
let person = {
sayName() {
return 'wang'
}
}
let friend = {
sayName() {
return 'li'
}
}
let p = Object.create(person)
console.log(p.sayName()) // wang
console.log(Object.getPrototypeOf(p) === person) // true
// 改变原型
Object.setPrototypeOf(p,friend)
console.log(p.sayName()) // li
console.log(Object.getPrototypeOf(p) === friend) // true
对象原型的真实值被储存在内部专用属性[[protơtype]]中,调用Object.getPrototypeOf()方法返回储存在其中的值,调用Object.setPrototypeOf()方法改变其中的值。然而,这不是操作[[prototype]]值的唯一方法
super引用
ES6引入了super引用,使开发者可以更便捷的访问对象原型。
如果想重写对象实例的方法,又需要调用与它同名的原型方法,利用super引用可以这样写
let person = {
sayName() {
return 'wang'
}
}
let friend = {
sayName() {
return super.sayName() + ' and li are good friends';
}
}
Object.setPrototypeOf(friend,person)
console.log(friend.sayName()) // wang and li are good friends
console.log(Object.getPrototypeOf(friend) === person) // true
super引用相当于指向对象原型的指针,实际上也就是Object.getPrototypeOf(this)的值
如果用es5来写这个示例,就要复杂一些,而且不容易理解
let person = {
sayName() {
return 'wang'
}
}
let friend = {
sayName() {
// 注意这里
return Object.getPrototypeOf(this).sayName.call(this) + ' and li are good friends';
}
}
Object.setPrototypeOf(friend,person)
console.log(friend.sayName()) // wang and li are good friends
console.log(Object.getPrototypeOf(friend) === person) // true
Super引用在多重继承情况下非常有用,因为在这种情况下,使用Object.getPrototypeOf()方法将会出现问题
let person = {
sayName() {
return 'wang'
}
}
let friend = {
sayName() {
return Object.getPrototypeOf(this).sayName.call(this) + ' and li are good friends';
}
}
Object.setPrototypeOf(friend,person)
let p = Object.create(friend)
console.log(p.sayName()) // Uncaught RangeError: Maximum call stack size exceeded
当执行p.sayName()方法时,会调用friend.sayName()方法,而此时的this值为p。Object.getPrototypeOf(this)又会返回friend对象。所以就会进入递归调用直到触发栈溢出报错
如果使用super引用就不会有这种问题
let person = {
sayName() {
return 'wang'
}
}
let friend = {
sayName() {
return super.sayName() + ' and li are good friends';
}
}
Object.setPrototypeOf(friend,person)
let p = Object.create(friend)
console.log(p.sayName()) // wang and li are good friends
super引用不是动态变化的,无论有多少其他方法继承了sayName()方法,super.sayName()始终指向person.sayName()方法
方法定义
在ES6以前从未正式定义过"方法"的概念,方法仅仅是一个具有功能而非数据的对象属性。而在ES6中正式将方法定义为一个函数,它会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象
let person = {
sayName() {
return 'wang'
}
}
let friend = {
sayName() {
return super.sayName() + ' and li are good friends';
}
}
Object.setPrototypeOf(friend,person)
console.log(friend.sayName())
当调用friend.sayName()方法时,该方法内部的[[HomeObject]]属性值是friend,friend的原型是person,所以super.sayName()等价于person.sayName().call(this)
对象遍历
ES5 引入了Object.keys()
方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
let obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)) // ["a", "b", "c"]
for (let key of Object.keys(obj)) {
console.log(key);
}
// a
// b
// c
ES8引入了Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用
Object.values()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值
let obj = { a: 1, b: 2, c: 3 };
console.log(Object.values(obj)) // [1, 2, 3]
for (let value of Object.values(obj)) {
console.log(value);
}
// 1
// 2
// 3
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
let obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)) // [["a", 1], ["b", 2], ["c", 3]]
for (let [key, value] of Object.entries(obj)) {
console.log([key, value]);
}
// ["a", 1]
// ["b", 2]
// ["b", 3]