对象类别
· 普通(Ordinary)对象 具有JavaScript对象所有的默认内部行为。
· 特异(Exotic)对象 具有某些与默认行为不符的内部行为。
· 标准(Standard)对象 ECMAScript 6规范中定义的对象,例如,Array、Date等。标准对象既可以是普通对象,也可以是特异对象。
· 内建对象 脚本开始执行时存在于JavaScript执行环境中的对象,所有标准对象都是内建对象。在整本书中,我们将使用这些术语来解释ECMAScript 6定义的各种对象。
对象字面量语法扩展
属性初始值的简写
es5中
function createPerson(name,age) {return {name:name,age:age};}
name和age分别重复了两遍,只是其中一个是对象属性名称,另一个是为属性赋值的变量
es6中简化写法
如果对象的属性与本地变量同名时,不用再写冒号和值,简单地只写属性名即可。
function createPerson(name,age) {return {name,age};}
当对象字面量里只有一个属性的名称时,js引擎会在可访问作用域中查找同名的变量
例如
const foo = 'bar';const baz = {foo};baz // {foo: "bar"}// 等同于const baz = {foo: foo};
function f(x, y) {return {x, y};}// 等同于function f(x, y) {return {x: x, y: y};}f(1, 2) // Object {x: 1, y: 2}
这种写法用于函数的返回值,将会非常方便。
function getPoint() {const x = 1;const y = 10;return {x, y};}getPoint()// {x:1, y:10}
对象方法的简写语法
es5
var person = {name: "abc",sayName: function () {console.log(this.name);}}
es6
去掉冒号与function关键字
var person = {name: "abc",sayName() {console.log(this.name);}}console.log(person.sayName.name); // sayName
通过对象方法简写语法创建的方法有一个name属性,其值为小括号前的名称,在上述示例中,person.sayName()方法的name属性的值为”sayName”。
可计算属性名
在ECMAScript 5及早期版本的对象实例中,如果想要通过计算得到属性名,就需要用方括号代替点记法。有些包括某些字符的字符串字面量作为标识符会出错,其和变量放在方括号中都是被允许的。请看这个示例:
变量lastName 等于 “last name”
var person = {},lastName = "last name";person["first name"] = "Nicholas";person[lastName] = "Zakas";console.log(person["first name"]); //Nicholasconsole.log(person[lastName]); // Zakasconsole.log(person["last name"]); // Zakas
变量lastName被赋值为字符串”last name”,引用的两个属性名称中都含有空格,因而不可使用点记法引用这些属性,却可以使用方括号,因为它支持通过任何字符串值作为名称访问属性的值。
var person = {"first name": "Nicholas"}console.log(person["first name"]);
这种模式适用于属性名提前已知或可被字符串字面量表示的情况。然而,如果属性名称”first name”被包含在一个变量中(就像之前示例中的那样),或者需要通过计算才能得到该变量的值,那么在ECMAScript 5中是无法为一个对象字面量定义该属性的
而在ECMAScript 6中,可在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同,也是使用方括号。(可以在对象里定义变量作为属性名)
let lastName = "last name";let person = {"first name": "Nicholas",[lastName]: "Zakas"}console.log(person["first name"]); //Zakasconsole.log(person[lastName]); //Zakas
在对象字面量中使用方括号表示的该属性名称是可计算的,它的内容将被求值并被最终转化为一个字符串,因而同样可以使用表达式作为属性的可计算名称,例如:
var suffix = " name";var person = {["first" + suffix]: "Nicholas",["last" + suffix]: "Zakas"}console.log(person["first name"]) //"Nicholas"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值。
