js 中的原型

要吃早餐發表於2018-04-05
  1. 理解原型物件

無論什麼時候,只要新建一個函式,就會根據特定規則為函式建立一個prototype 屬性,這個屬性指向這個函式的原型物件。所有原型物件都會自動獲得一個constructor 屬性,這個屬性指向prototype屬性所在的函式。

建立來自定義的建構函式後,其原型物件預設只會取得constructor屬性,至於其它方法,都是從 Object 繼承而來。

呼叫建構函式建立例項後,例項的內部會有一個指標指向該函式的原型物件。ECMA-262第5版中將這個指標叫做 [[ Prototype ]] ,雖然在指令碼中沒有標準的方式訪問這個指標,但 Firefox 、Safari、chrome在每個物件上都支援一個屬性 _proto_。 很重要的一點就是,這個連線存在與例項與建構函式的原型物件之間,而不是存在於例項與建構函式之間

function Person() {}

Person.prototype.name = "張三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

console.log(Person.prototype.isPrototypeOf(person1)); // true
複製程式碼

雖然在所有實現中都無法訪問到 [[ Prototype ]] ,但可以通過 isPrototypeOf() 方法來確定物件之間是否有這種關係。如果 [[ Prototype ]] 指向呼叫 isPrototypeOf()方法的物件,那麼這個方法就返回 true。

ECMAscript 5 新增加了一個方法Object.getPrototypeOf()。這個方法返回 [[ Prototype ]] 的值。

console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
console.log(Object.getPrototypeOf(person1).name); // 張三
複製程式碼

使用 Object.getPrototypeOf()可以很方便的取得一個物件的原型,這在利用原型實現繼承的情況下是非常重要的。

當程式碼讀取某個物件的某個屬性時,都會執行一次搜尋,目標是具有給定名字的屬性,搜尋首先從物件例項本身開始,如果在例項中找到了這個屬性,就返回屬性值,如果沒有找到,則繼續搜尋指標指向的原型物件,在原型物件中查詢這個屬性,找到後就返回,找不到就報錯。這就是多個物件例項共享原型所儲存的屬性和方法的基本原理。

雖然可以通過物件例項訪問儲存在原型中的值,但卻不能通過物件例項重寫原型中的值。如果在例項中新增一個跟原型物件中的屬性同名的屬性,那麼這個屬性就會新增到例項上,並且該屬性會遮蔽原型中的那個屬性。

function Person() {}

Person.prototype.name = "張三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.brother = ['李四', '王五'];
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

person2.brother = ['李四', '馬六'];
console.log(person1.brother); // [ '李四', '王五' ]
console.log(person2.brother); //[ '李四', '馬六' ]
複製程式碼

上面的例子中,我們為 person2 新增了一個 brother 屬性,而這個屬性與原型物件的屬性 brother 同名,這樣當我們訪問person2的 brother 屬性時,就會返回新增在 person2 例項上的 brother 屬性。而原型中的 brother 屬性也並沒有被重寫。

使用 hasOwnProperty()方法可以檢測一個屬性是存在於例項中,還是存在於原型中。這個方法只在給定屬性存在於例項中時,才會返回 true。

function Person() {}

Person.prototype.name = "張三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.brother = ['李四', '王五'];
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

person1.name = '李斯特';
console.log(person1.hasOwnProperty('name')); // true
console.log(person2.hasOwnProperty('name')); // false
複製程式碼
  1. 原型與 in 操作符

有兩種方式使用 in 操作符,單獨使用 和 在 for-in中使用。單獨使用時,in 操作符會在通過物件能夠訪問給定屬性時返回 true;無論該屬性是存在於例項中,還是存在於原型中。

利用 hasOwnProperty() 方法和 in 操作符就可以判斷屬性是存在於例項上,還是存在於原型中。

function testPropertyOf (object, name) {
    if (!(name in object)) {
        console.log('不存在這個屬性');
    } else if (!object.hasOwnProperty(name) && (name in object)) {
        console.log('這個屬性存在於原型上');
    } else if (object.hasOwnProperty(name) && (name in object)){
        console.log('這個屬性存在於例項中');
    }
}
複製程式碼

在使用 for-in 迴圈時,返回的是所有能夠通過物件訪問的、可列舉的( 內部屬性 enumerable 為 true)屬性,既包括例項中的屬性,也包括原型中的屬性。

要取得物件上可列舉的例項屬性,可以使用 Object.keys()方法,這個方法接受一個物件作為引數,返回一個包含所有可列舉屬性的字串陣列。

function Person() {}

Person.prototype.name = "張三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.brother = ['李四', '王五'];
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

person1.name = '李斯特';

let tempArr = [];
for (var keyName in person1) {
    tempArr.push(keyName);
}
let arr2 = Object.keys(person1);
console.log(tempArr);// [ 'name', 'age', 'job', 'brother', 'sayName' ]

console.log(arr2); // [ 'name' ]
複製程式碼

如果想得到所有屬性,不論是否可以列舉,可以使用Object.getOwnPropertyNames() 方法。

相關文章