prototype (原型) 屬性

這篇文章是基于 <<JavaScript 高級程序設(shè)計>>第六章面向?qū)ο蟮某绦蛟O(shè)計 6.2.3原型模式

我們每創(chuàng)建一個函數(shù), 都有一個 prototype (原型)屬性, 這個屬性是一個指針, 指向一個對象,這個對象的 用途: 包含可以由指定類型的所有實例共享的屬性和方法
所以 prototype 就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的哪個對象實例的原型對象

function Person() {};
Person.prototype.name = 'TonNi';
Person.prototype.age = 19;
Person.prototype.sayName = function () {
    alert ( this. name );
}

var person1 = new Person();
person1.sayName() // TonNi;

// prototype.isPrototypeOf 判斷 person1 實例的原型是不是 Person
console.log (Person.prototype.isPrototypeOf(person1))  // true

//  Object.getPrototypeOf 該方法獲取一個對象的原型
console.log(Object.getPrototypeOf(person1) === Person.prototype) // true 
console.log(Object.getPrototypeOf(person1).name) //TonNi

// hasOwnProperty 檢測一個實例屬性是不是存在與實例中 , 存在返回true
person1.sing = 'i can sing';
console.log(person1.hasOwnProperty('name')) // false 
console.log(person1.hasOwnProperty('sing')) // true 
1: 什么是實例, 什么是原型 ?

以上面的代碼為例,
原型: 就是 大寫的 Person 函數(shù)
實例: 就是以 Person為原型 從新 new 出來的person; 上例中的 person1就是實例

這里我們將 name, age, sayName() 等屬性和方法 直接添加到了 Personprototype 中, 這樣所有 new 出來的實例 都擁有同樣的 屬性和方法, 可以被訪問或調(diào)用. var person2 = new Person() person2 和 person1 一樣擁有同樣的屬性和方法

2: constructor

只要創(chuàng)建一個新的函數(shù), 就會根據(jù)特定規(guī)則為該函數(shù)創(chuàng)建一個 prototype 屬性, 此屬性指向函數(shù)的原型對象
而在默認情況下, 所有的原型對象都會自動獲得一個 constructor (構(gòu)造函數(shù)) 屬性, 這個屬性是一個指向 prototype 屬性所在函數(shù)的指針.
可以看下圖, Person.prototype.constructor 指向的就是 Person

結(jié)構(gòu)

作用:
通過這個構(gòu)造函數(shù), 我們還可以繼續(xù)為原型對象添加其他屬性和方法

3: 如果實例中和原型都有同名的屬性或者方法, 調(diào)用哪個呢?

當代碼讀取某個屬性或者方法時, 都會經(jīng)行一次搜索, 搜索目標是給定的屬性或方法的名字. 搜索順序是, 先從實例查找 , 如果實例中有, 則返回屬性值或者調(diào)用方法, 如果沒有, 則搜索指針指向原型對象, 如果有同上, 如果沒有, 屬性就返回 undefind, 方法則報錯, xxx is not a function

function Person() {};
Person.prototype.name = 'TonNi';
Person.prototype.age = 19;
Person.prototype.sayName = function () {
    alert ( this. name );
}

var person1 = new Person();
console.log(person1.name) //  'TonNi'   實例中沒有則去原型中找, 找到后返回屬性值,

person1.name = 'Mike'
console.log(person1.name) // Mike 實例中有了name 就直接拿來用了

//在實例化一個person2
var person2 = new Person()
console.log(person2 .name) //  'TonNi'   實例中沒有則去原型中找, 找到后返回屬性值, 不會因為 person1添加了name收到影響

// 刪除person1中的name
delete person1.name;
console.log(person1.name) //  'TonNi'  刪除后 實例中沒有則去原型中找, 找到后返回屬性值,

故當我們?yōu)閷嵗砑右粋€屬性時,此屬性會屏蔽掉原型中對象中的同名屬性, 但是只會阻止我們訪問對象原型屬性, 并不會修改

4: 關(guān)于判斷屬性或方法是存在實例還是原型中
  • hasOwnProperty() 只有屬性或方法 在實例中才為 true
...上面的例子
person1.hasOwnproperty('name');  //  false  不在實例中

person1.name = 'Mike';
person1.hasOwnProperty('name'); // true 在實例中, 返回true
  • in操作符
    無論該屬性在實例中還是原型中, 只要能訪問到就返回 true
...上上面的例子
'name' in person1 // true;
person1.hasOwnproperty('name');  //  false  不在實例中

person1.name = 'Mike';
'name' in person1 // true;
person1.hasOwnProperty('name'); // true 在實例中, 返回true

'run' in person1; // false 實例和原型都沒有 

所以呢, 同時使用 hasOwnProperty()in 就可以確定該屬性到底存在對象還是原型中了

function hasPrototypeProperty (object, name) {
  return !object.hasOwnProperty(name) && (name in object);
}

由于 in 操作符只要通過對象能夠訪問到屬性就能返回 true, hasOwnProperty() 只要屬性存在實例中才能返回 true, 所以只要 in 返回 truehasOwnProperty() 返回 false 就說明屬性在原型中

5: 更簡單的原型語法

由于上面例子每添加一個屬性就敲一次 Person.prototype 很麻煩, 為了減少不必要的輸入, 如下:

function Person () {}
Person.prototype = {
    name: 'Toni',
    age: 19,
    sayName: function () {
        console.log(this.name);
    }
}

缺點1:
按照上面的對象字面量來重寫整個原型對象, 最終結(jié)果相同, 但是, constructor 屬性不在指向 Person, 這里重寫了后, constructor 指向了新對象 Object構(gòu)造函數(shù) 不在指向 Person, 盡管 instanceof 還能返回正確的結(jié)果, 但是 constructor 已經(jīng)無法確定對象類型了
如:

//上例
var friend = new Person();
console.log(friend  instanceof Object)// true
console.log(friend  instanceof Person)// true

console.log(friend.constructor == Person) // false  正常的此處應(yīng)為true
console.log(friend.constructor == Object) // true

改進:
如果你的確需要 constructor屬性的話, 可以這樣:

function Person () {}
Person.prototype = {
    constructor: Person,     //這里 
    name: 'Toni',
    age: 19,
    sayName: function () {
        console.log(this.name);
    }
}

或者 使用 Object.defineProperty()

function Person () {}
Person.prototype = {
    name: 'Toni',
    age: 19,
    sayName: function () {
        console.log(this.name);
    }
}

Object.defineProPerty (Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
})
  • 第一種方法: 有個問題就是, 把原本 constructor 屬性的 Enumerable 設(shè)置成了 true 成了可枚舉的屬性, 原生的是 false 不可枚舉
  • 第二中方法, 則是重構(gòu)構(gòu)造函數(shù), 直接設(shè)置了 enumerable: false, 但是 只適應(yīng)于 ECMAScript 5 兼容的瀏覽器

缺點2或者是需注意:
看例子:
例子1

var friend = new Person();    // 先實例化一個Person原型
Person.prototype.sayHi = function () { 
    console.log('Hi')
};                                        // 添加方法

friend.sayHi();  // Hi     沒什么問題

例子2:

function Person () {};    //原型

var friend = new Person(); //  緊接著實例化  (注意這里實例的順序)

Person.prototype = {
    constructor: Person,    
    name: 'Toni',
    age: 19,
    sayName: function () {
        console.log(this.name);
    }
}                                             // 然后在添加屬性方法

friend.sayName()              // 報錯 friend.sayName is not a function

例子2正確的順序是:

function Person () {};    //原型

Person.prototype = {
    constructor: Person,   
    name: 'Toni',
    age: 19,
    sayName: function () {
        console.log(this.name);
    }
}                                             // 然后在添加屬性方法

var friend = new Person();    //  實例化

friend.sayName()              //  'Toni';

例子錯誤原因

例子1中, 因為實例與原型中鏈接的是一個指針, 而非一個副本, 所以不論實例化原型與添加屬性方法的順序如何, 都是可以在原型中獲取到

例子2中, 我們先創(chuàng)建一個Person 的一個實例, 然后又重寫了其原型對象,
但是, 重寫原型對象就切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對象實例之間的聯(lián)系

6: 原型對象的問題

原型對象的缺點 , 所有的實例都在默認情況下獲取相同的屬性值, 即共享本性, 雖然這也是其優(yōu)點, 但是這中共享對于函數(shù)非常適合, 對于包含基本值(即 name: 'tom',)的屬性還算可以, 但是對于引用類型(Object, Array,String, ...)的屬性問題就比較突出了
如:

function Person () {};    

Person.prototype = {
    constructor: Person,   
    name: 'Toni',
    age: 19,
    friends: ['Tom', 'mike', 'Anny'],
    sayName: function () {
        console.log(this.name);
    }
}                                             

var friend1 = new Person();   
var friend2 = new Person();   

console.log (friend2 .friends) //'Tom', 'mike', 'Anny'   正常顯示

friend1 .friends.push('Masa');   // 使用實例1 在數(shù)組中添加一個人名 

console.log (friend2 .friends) //'Tom', 'mike', 'Anny' ,'Masa'  , person2受到了影響

當然了如果你要的結(jié)果就是這樣, 那倒無所謂, 如果不是, 都需要單獨的屬性的話, 就有很大問題,

問題原因自然是由于實例與原型中鏈接的是一個指針, 指向同一個數(shù)組, friend1 對原型的修改, friend2 也會反映出來

7: 所以最好組合是

組合使用 構(gòu)造函數(shù)模式 和原型模式,
構(gòu)造函數(shù)模式用于定義實例屬性,
原型模式用于定義方法和共享屬性,

// 構(gòu)造函數(shù), 定義實例屬性,  this將作用域指向新對象
function Person (name,age) {
    this.name = name;
    this.age = age;
    this.friends = ['Tom', 'Mike'];
}

//原型模式用于定義方法和共享屬性,
Person.prototype = {
    constructor: Person,
    sayName: function () {
        console.log(this.name)
    }
}

var person1 = new Person ('Toni', 19) ;
var person2 = new Person ('Anny', 18);

person1.friends.push('An');
console.log(person1.friends);  // 'Tom', 'Mike','An'
console.log(person2.friends) ; //  'Tom', 'Mike', 

例子中, 實例屬性都是在構(gòu)造函數(shù)中定義的, 而由所有實例共享的屬性 construction 和 方法 sayName 都是在原型中定義的, 因而, 修改 person1.friends 數(shù)組, 只是修改實例中的數(shù)組, 沒有修改共有屬性,, 所以其他實例不會受到影響

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容