js中創建對象方式----原型模式

一、什麼是原型模式

在js中,創建對象的方式有工廠模式和構造函數模式等; 而構造函數模式最大的問題在於:構造函數中的每個方法都需要在實例對象中重新創建一遍,不能複用,所以爲了解決這一個問題,就需要使用原型模式來創建對象。
原型模式是把所有實例共享的方法和屬性放在一個叫做prototype(原型)的屬性中 ,在創建一個函數時都會有個prototype屬性, 這個屬性是一個指針,指向一個對象,是通過調用構造函數而創建的那個對象實例的原型對象

 // 構造函數
    function Person() {};

    // 原型屬性prototype
    Person.prototype.name = '張三';
    Person.prototype.sayName = function() {
        console.log(this.name);
    };

    let person1 = new Person();
    person1.sayName(); //張三
    let person2 = new Person();
    person2.sayName(); // 張三

    console.log(person1.sayName == person2.sayName); //true
  1. 理解原型對象

    無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,這個屬性指向函數的原型對象,在默認的情況下,所有的原型對象都自動獲得一個constructor(構造函數)屬性,這是一個指針,指向prototype屬性所在的函數。創建了自定義的構造函數之後,其原型對象默認只會取得constructor屬性;其他的方法則是從Object繼承來的。
    當調用構造函數創建一個新實例對象後,該實例的內部將包含一個指針[[Prototype]],指向構造函數的原型對象。這個連接存在於實例和構造函數的原型對象之間,而不是存在實例和構造函數之間。
    每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了就返回該屬性的值,沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性,如果在原型對象中找到了這個屬性,就返回該屬性的值。
    雖然可以通過實例訪問保存在原型中的值,但不能通過實例對象重寫原型中的值,如果在實例中添加一個在原型中的同名屬性,該屬性會自動屏蔽原型中的屬性,但是不會修改原型中的屬性,只會阻止訪問原型中的屬性,通過delete操作符則可以完全刪除實例屬性,使得可以重新訪問原型中的屬性。

  2. 原型與in操作符

    hasOwnProperty()方法可以檢測一個屬性是否存在於實例對象中,
    // 構造函數
    function Person() {
    this.age = 16;
    };
    Person.prototype.name = "張三";
    let person1 = new Person();
    console.log(person1.hasOwnProperty('name')); // false
    console.log(person1.hasOwnProperty('age')); // true

    in操作符的使用可以分爲兩類,單獨使用和在for-in循環使用,在單獨使用時,in操作符會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在於實例中還是原型中。
    // 構造函數
    function Person() {}
    Person.prototype.name = 'zhang';
    let person1 = new Person();
    console.log('name' in person1); // true
    person1.age = 14;
    console.log('age' in person1); // true
    同時使用hasOwnProperty()方法和in操作符,可以確定該屬性時在原型上還是在存在於對象中。
    // 構造函數
    function Person() {}
    function hasPrototypeProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
    }
    Person.prototype.name = "張三";
    let person = new Person();
    console.log(hasPrototypeProperty(person, 'name')); // true
    console.log(hasPrototypeProperty(person, 'age')); // false
    使用for-in循環時,返回的是所有能夠通過對象訪問的、可枚舉的屬性,其中即包含存在於實例中的屬性,也包含與存在原型中的屬性。
    let o = {
    name: 'san',
    age: 14,
    };
    for(let key in o) {
    console.log(key);
    }
    要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法,接收一個對象作爲參數,返回一個包含所有可枚舉屬性的字符串數組。
    如果想得到所有實例屬性。無論是否可枚舉,都可以使用Object.getOwnPropertyNames()方法。

  3. 更簡單的原型語法

    爲了減少不必要的輸入和從視覺上更好的封裝原型的功能,常見的做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象。
    // 構造函數
    function Person() {};
    Person.prototype = {
    sayHi: function() {
    console.log(hi);
    },
    name: '張三',
    };
    通過這個方式會導致原型對象中的constructor屬性不在指向Person了。如果constructor的值真的很重要,可以像下面這樣特意將它設置回適當的值。
    // 構造函數
    function Person() {};
    Person.prototype = {
    constructor: Person,
    sayHi: function() {
    console.log(hi);
    },
    name: '張三',
    };
    但是通過這種方式會導致對象的[[Enumerable]]特性被設置爲ture,默認情況下,constructor屬性時不可枚舉的,可以通過Object.defineProperty()解決這個問題。
    // 構造函數
    function Person() {};
    Person.prototype = {
    sayHi: function() {
    console.log(hi);
    },
    name: '張三',
    };
    Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
    }

  4. 原型的動態性

    當對原型對象所做的任何修改都能夠立即從實例上反應出來。
    function Person() {};
    var friend = new Person();
    Person.prototype.sayHi = function() {
    console.log('hi');
    };
    friend.sayHi(); // hi

    但是如果是重寫整個原型對象,那麼情況就不一樣了。調用構造函數時會爲實例添加一個指向最初原型的[[prototype]]指針,而把原型修改爲另外一個對象 就相當於切斷了構造函數與最初原型之間的聯繫。 實例中的指針僅指向原型,而不是指向構造函數。
    // 構造函數
    function Person() {};
    var friend = new Person();
    Person.prototype = {
    constructor: Person,
    sayHi: function() {
    console.log(hi);
    }
    };
    friend.sayHi(); // Uncaught TypeError: friend.sayHi is not a function

    創建了一個Person的實例,然後又重寫了其原型對象。但是在使用sayHi()時發生了錯誤,這個時候實例所指向的原型對象是一個新的對象。重寫原型對象切斷了現有原型與之前已經存在的對象實例直接的聯繫。所以報錯了。

  5. 原生對象的原型

    原型模式的重要性不僅體現在創建自定義類型方面,就連所有原生的引用類型,都採用這種模式,所有的原生引用類型(Object、Array、String)等,都在其構造函數的原型上定義了方法。可以像修改自定義對象的原型一樣修改原生對象的原型。

二、原型模式的缺點

對於包含引用類型值的屬性來說,所有實例在默認的情況下都會取得相同的屬性值。
// 構造函數
function Person() {};
// 原型屬性prototype
Person.prototype = {
constructor: Person,
friends: ['張三', '李四'],
}
let person1 = new Person();
let person2 = new Person();
person1.friends.push('王五');
console.log(person1.friends); // ["張三", "李四", "王五"]
console.log(person2.friends); // ["張三", "李四", "王五"]

由於friends存在於Person的原型對象中,所以person1對friends的修改也會通過person2反應出來,但是實例對象一般都是要有屬於自己的全部屬性,正因爲如此,很少有人單獨使用原型模式來創建對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章