1. 程式人生 > >ES6之擴充套件物件的功能性

ES6之擴充套件物件的功能性

目錄

一、物件類別

二、物件字面量的語法擴充套件

2.1、屬性初始值的簡寫

2.2、物件方法的簡寫語法

2.3、可計算屬性名

三、新增方法

3.1、Object.is()方法

3.2、Object.assign()方法

四、重複的物件字面量屬性

五、自有屬性列舉順序

六、增強物件原型

6.1、改變物件的原型

6.2、簡化原型訪問的Super引用

七、正式方法的定義


    ECMAScript6通過多種方式來加強物件的使用,通過簡單的語法擴充套件,提供更多操作物件及與物件互動的方法。

一、物件類別

    ES6

規範清晰定義了每一個類別的物件:

  • 普通物件    ——    具有JavaScript物件所有的預設內部行為
  • 特異物件    ——    具有某些與預設行為不符的內部行為
  • 標準物件    ——    ES6規範中定義的物件,例如,ArrayDate等。標準物件既可以是普通物件,也可以是特異物件
  • 內建物件    ——    指令碼開始執行時存在於JavaScript執行環境中的物件,所有標準物件都是內建物件

    關係圖:

二、物件字面量的語法擴充套件

    ES6通過下面的幾種語法,讓物件字面量變得更強大、更簡潔。

2.1、屬性初始值的簡寫

    在ES5及更早版本中,物件字面量中初始化屬性值時會有一些重複,例如:

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

    這段程式碼中的createPerson()

函式建立了一個物件,其屬性名稱與函式的引數相同,在返回的結果中,nameage分別重複了兩遍。

    在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物件,當執行relativegetGreeting方法時,會呼叫friendgetGreeting()方法,而此時的this值為relativeObject.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!"