ES6之擴充套件物件的功能性
目錄
ECMAScript6通過多種方式來加強物件的使用,通過簡單的語法擴充套件,提供更多操作物件及與物件互動的方法。
一、物件類別
ES6
- 普通物件 —— 具有JavaScript物件所有的預設內部行為
- 特異物件 —— 具有某些與預設行為不符的內部行為
- 標準物件 —— ES6規範中定義的物件,例如,Array、Date等。標準物件既可以是普通物件,也可以是特異物件
- 內建物件 —— 指令碼開始執行時存在於JavaScript執行環境中的物件,所有標準物件都是內建物件
關係圖:
二、物件字面量的語法擴充套件
ES6通過下面的幾種語法,讓物件字面量變得更強大、更簡潔。
2.1、屬性初始值的簡寫
在ES5及更早版本中,物件字面量中初始化屬性值時會有一些重複,例如:
function createPerson(name, age){
return {
name: name,
age: age
};
}
這段程式碼中的createPerson()
在ES6中,通過使用屬性初始化的簡寫語法,可以消除這種屬性名稱與區域性變數之間的重複書寫,例如:
function createPerson(name, age){
return {
name,
age
};
}
2.2、物件方法的簡寫語法
在ES5及早期版本中,如果為物件新增方法,必須通過指定名稱並完整定義函式來實現,例如:
var person = {
name: "Nicholas",
sayName: function(){
console.log(this.name);
}
};
而在ES6中,語法更簡潔,消除了冒號和function關鍵字,例如:
var person = {
name: "Nicholas",
sayName(){
console.log(this.name);
}
};
2.3、可計算屬性名
ES6中,可在物件字面量中使用可計算屬性名稱,其語法與引用物件例項的可計算屬性名稱相同,也是使用方括號。
let lastName = "last name";
let time = "current ";
let person = {
"first name": "Nicholas",
[lastName]: "Zakas",
[time + "age"]: 30
};
三、新增方法
在ES6中,為了使某些任務更易完成,在全域性Object物件上引入了一些新方法。
3.1、Object.is()方法
ES6引入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(5 === 5); // true
console.log(5 === "5"); // false
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, "5")); // false
大可不必拋棄等號運算子,是否選擇用Object.is()方法而不是==或===取決於那些特殊情況如何影響程式碼。
3.2、Object.assign()方法
混合(Mixin)是JavaScript中實現物件組合最流行的一種模式。
在一個mixin方法中,一個物件接收來自另一個物件的屬性和方法,許多JavaScript庫中都有類似的mixin方法:
function mixin(receiver, supplier){
Object.keys(supplier).forEach(function(key){
receiver[key] = supplier[key];
});
return receiver;
}
這種混合模式非常流行,因而ES6添加了Object.assign()方法來實現相同的功能。
這個方法接受一個接收物件和任意數量的源物件,最終返回接收物件。
示例:
var receiver = {};
Object.assign(receiver, {
type: "js",
name: 'file.js'
});
console.log(receiver.type); // "css"
console.log(receiver.name); // "file.js"
注意:
Object.assign()方法不能將提供者的訪問器屬性複製到接收物件中。
由於Object.assign()方法執行了賦值操作,因此提供者的訪問器屬性最終會轉變為接收物件中的一個數據屬性。
示例:
var receiver = {},
supplier = {
get name(){
return "file.js"
}
};
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value); // "file.js"
console.log(descriptor.get); // undefined
四、重複的物件字面量屬性
ES5嚴格模式中加入了物件字面量重複屬性的校驗,當同時存在多個同名屬性時會丟擲錯誤。
示例:
"use strict";
var person = {
name: 'Nicholas',
name: 'Greg' // ES5嚴格模式下會有語法錯誤
};
但是,在ES6中重複屬性檢查被移除了。
無論是在嚴格模式還是非嚴格模式下,程式碼不再檢查重複屬性,對於每一組重複屬性,都會選取最後一個取值。
"use strict";
var person = {
name: 'Nicholas',
name: 'Greg' // ES6嚴格模式下沒有錯誤
};
console.log(person.name); // "Greg"
五、自有屬性列舉順序
ES6嚴格規定了物件的自由屬性被列舉時的返回順序。
這會影響到Object.getOwnPropertyNames()方法及Reflect.ownKeys返回屬性的方式,Object.assign()方法處理屬性的順序也將隨之改變。
自有屬性列舉順序的基本規則是:
- 所有數字鍵按升序排序
- 所有字串鍵按照它們被加入物件的順序排序
- 所有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"
對於for-in迴圈,由於並非所有廠商都遵循相同的實現方式,因此仍未指定一個明確的列舉順序。
而Object.keys()方法和JSON.stringify()方法都指明與for-in使用相同的列舉順序,因此它們的列舉順序目前也不明晰。
六、增強物件原型
6.1、改變物件的原型
在ES5中,添加了Object.getPrototypeOf()方法來返回任意指定物件的原型,但仍缺少物件在例項化後改變原型的標準方法。
所以,在ES6中添加了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()); // "Woof"
console.log(Object.getPrototypeOf(friend) === dog); // true
物件原型的真實值被儲存在物件例項內部專用屬性[[Prototype]]中,呼叫Object.getPrototypeOf()方法返回儲存在其中的值,呼叫Object.setPrototypeOf()方法改變其中的值。
6.2、簡化原型訪問的Super引用
ES6引入了Super引用的特性,使用它可以更便捷地訪問物件原型。
例如,如果想重寫物件例項的方法,又需要呼叫與它同名的原型方法,則在ES5中可以這樣實現:
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) === dog); // true
要準確記得如何使用Object.getPrototypeOf()方法和.call(this)方法來呼叫原型上的方法實在有些複雜,所以ES6引入了super關鍵字。
簡單來說,super引用相當於指向物件原型的指標,實際上也就是Object.getPrototypeOf(this)的值。
於是,上面的程式碼可以這樣簡化為:
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) === dog); // true
注意:必須要在使用簡寫方法的物件中使用super引用,但如果在其他方法宣告中使用會導致語法錯誤,就像這樣:
let friend = {
getGreeting: function(){
return super.getGreeting() + ", hi!"; // 語法錯誤
}
};
super引用在多重繼承的情況下非常有用,因為在這種情況下,使用Object.getPrototypeOf()方法將會出現問題:
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!
relative的原型是friend物件,當執行relative的getGreeting方法時,會呼叫friend的getGreeting()方法,而此時的this值為relative,Object.getPrototypeOf(this)又會返回friend物件。所以就會進入遞迴呼叫直到觸發棧溢位報錯。
在使用ES6中,使用super引用便可以迎刃而解:
let person = {
getGreeting(){
return "Hello";
}
};
// 以person物件為原型
let friend = {
getGreeting(){
return super.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()); // "Hello hi!"
super引用不是動態變化的,它總是指向正確的物件。
在這個示例中,無論有多少其他方法繼承了getGreeting方法,super.getGreeting()始終指向person.getGreeting()方法。
七、正式方法的定義
ES6以前從未正式定義“方法”的概念,方法僅僅是一個具有功能而非資料的物件屬性。
而在ES6中正式將方法定義為一個函式,它會有一個內部的[[HomeObject]]屬性來容納這個方法從屬的物件。
let person = {
// 是方法
getGreeting(){ // 內部有[[HomeObject]]屬性
return "Hello";
}
};
// 不是方法
function shareGreeting(){ // 沒有[[HomeObject]]屬性
return "Hi!";
}
由於getGreeting()方法直接把函式賦值給了person物件,因而getGreeting()方法的[[HomeObject]]屬性值為person。
而建立shareGreeting()函式時,由於未將其賦值給一個物件,因而該方法沒有明確定義[[HomeObject]]屬性。
在大多數情況下這點小差別無關緊要,但是當使用super引用時就變得非常重要了。
super的所有應用都通過[[HomeObject]]屬性來確定後續的執行過程。
第一步是在[[HomeObject]]屬性上呼叫Object.getPrototypeOf()方法來檢索原型的引用;然後搜尋原型找到同名函式;最後,設定this繫結並且呼叫相應的方法。
示例:
let person = {
// 是方法
getGreeting(){ // 內部有[[HomeObject]]屬性
return "Hello";
}
};
// 以person物件為原型
let friend = {
// 是方法
getGreeting(){
return super.getGreeting() + ", hi!"; // 內部有[[HomeObject]]屬性
}
}
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting()); // "Hello, hi!"