拋開「構造」二字,只要建立了一個函數,就會有與其對應的原型對象。其關係以下:前端
/* 函數 */
function Person () { ... }
/* 原型對象 */
Person.prototype = {
constructor: Person
}
複製代碼
函數 Person
的 prototype
屬性指向其原型對象,原型對象 Person.prototype
的 constructor
屬性又反過來指向了函數。兩者經過這種關係彼此關聯起來。函數
回到構造函數上來。若是此時咱們使用構造函數 Person
新建了一個實例 person1
,實例與構造函數間是沒有直接聯繫的,實例的 __proto__
屬性指向構造函數的原型對象 Person.prototype
。所以,訪問實例的構造函數,只能經過間接地在原型對象中拿到 constructor
屬性。ui
var person1 = new Person();
console.log( person1.__proto__ === Person.prototype ); /* true */
console.log( person1.__proto__.constructor === Person ); /* true,此處僅僅是爲了演示,實際上沒必要顯式地寫 __proto__ ,JS會自動地去原型鏈上尋找 constructor 屬性 */
複製代碼
除了使用字面量建立對象之外,工廠模式是最簡單的建立對象的方法。this
function createPerson (name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
console.log(this.name);
};
return o;
}
var person1 = createPeron('Nicholas', 29);
var person2 = createPeron('Grey', 27);
複製代碼
使用工廠模式,每次老是新建並返回一個全新的對象。能夠看出,person1
與 person2
僅僅是有相同名字的屬性和函數,但兩者之間沒有任何關聯,與 createPerson
工廠函數不存在關係,更沒有原型對象,所以咱們沒法識別其類型。spa
使用 new
操做符,能夠將普通函數用做構造函數。其實是將 Person
在 new
的空白對象做用域中執行。prototype
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
};
}
var person1 = new Person('Nicholas', 29);
var person2 = new Person('Grey', 27);
複製代碼
這裏正是咱們在第一節中描述的構造函數、原型對象和實例的關係。構造函數 Person
有一個原型對象 Person.prototype
,兩個實例 person1
和 person2
的 __proto__
均指向了這個原型對象,實例經過原型對象訪問到構造函數 Person
,藉助於這個關係能夠解決新建對象的類型識別問題。code
但在構造函數中的 sayName
方法,實際上是每次 new
時新建的,兩個函數是在內存中獨立存在。對象
console.log( person1.sayName === person2.sayName ); /* false */
複製代碼
因爲其完成相同的功能,應將其視爲一個「公共函數」。完成相同功能的「公共函數」,不必在內存中存在兩個副本。把 sayName
的定義轉移到構造函數外能夠解決這個問題。ip
function sayName () {
console.log(this.name);
}
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
var person1 = new Person('Nicholas', 29);
var person2 = new Person('Grey', 27);
console.log( person1.sayName === person2.sayName ); /* true */
複製代碼
公共函數 sayName
如今在內存中僅有一個副本了。但新的問題出現了,它被暴露在全局環境下,任何對象均可調用它。這不應是一個對象的內部函數該有的狀況。繼續改進,使用原型模式!內存
前面咱們提到,每一個構造函數都有一個原型對象,實例能夠經過 __proto__
訪問原型對象。當訪問實例的屬性時,JS 會藉助 __proto__
自動地在原型鏈上一層層地向上尋找目標屬性名對應的屬性值。
因爲構造函數在內存中只有一份,因此原型對象也只有一份。原型模式就是把屬性和方法都定義在原型對象中。
function Person () {
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = age;
Person.prototype.sayName = function () {
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
複製代碼
基於原型鏈的特性,當咱們訪問實例 person1
的 name
屬性時,返回的是原型對象中的 name
屬性。因爲原型對象只有一份,因此使用構造函數 Person
建立的實例共享同一份屬性和函數。
顯然,咱們但願每一個實例有本身獨立的屬性。能夠在實例中建立同名屬性,在原型鏈的最前端「屏蔽」原型對象中的屬性,這樣建立的屬性就是屬於每一個實例的了。
console.log(person1.name); /* Nicholas */
console.log(person2.name); /* Nicholas */
person1.name = 'Rob';
console.log(person1.name); /* Rob - 實例屬性 */
console.log(person2.name); /* Nicholas - 原型對象屬性 */
delete person1.name; /* delete 用於刪除實例屬性 */
console.log(person1.name); /* Nicholas - 原型對象屬性 */
console.log(person2.name); /* Nicholas - 原型對象屬性 */
複製代碼
這種「添加同名屬性」來「隱藏原型屬性」的方法對於基本類型來說勉強能夠使用。然而,在這個問題上,給引用類型帶來的麻煩更爲突出。
function Person () {
}
Person.prototype.name = 'Nicholas';
Person.prototype.friends = ['Shelby', 'Court'];
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('Van');
console.log(person1.friends); /* 'Shelby, Court, Van' */
console.log(person2.friends); /* 'Shelby, Court, Van' */
複製代碼
咱們使用原型模式的初衷是,讓須要共享的方法在實例間共享。使人苦惱的是,不須要共享的屬性也被共享了。有沒有一種方法能夠自由地決定屬性的共享/不共享呢?
咱們在使用構造函數時,內部函數 sayName
是非共享的,每一個實例擁有一份 sayName
函數的副本。準確的講,構造函數模式下全部的屬性和方法都是在實例中定義的,所以是非共享的。咱們利用這一點來改造原型模式。
核心思想是,將非共享屬性放在構造函數中定義,最終這些非共享屬性將各自屬於本身的實例;將方法和共享屬性放在原型對象中,全部實例共同使用一份副本。
function Person (name, age) {
this.name = name;
this.age = age;
this.friends = ['Shelby', 'Court'];
}
Person.prototype.sayName = function () {
console.log(this.name);
};
var person1 = new Person('Nicholas', 29);
var person2 = new Person('Greg', 27);
複製代碼
這是目前 ECMAScript 中使用最普遍、認同度最高的一種建立自定義類型的方法。
上面咱們介紹的原型模式,都是在 Person.prototype
直接添加屬性/方法,寫法是這樣的:
Person.prototype.name = 'Nicholas';
Person.prototype.age = 27;
Person.prototype.sayName = function () {
console.log(this.name);
};
複製代碼
若是咱們使用字面量的方式重寫,代碼會簡介得多:
Person.prototype = {
name: 'Nicholas',
age: 27,
sayName: function () {
console.log(this.name);
};
}
複製代碼
但回想第一節介紹的知識,原型對象 Person.prototype
的屬性 constructor
指向了構造函數 Person
,實例 person1
的 __proto__
屬性指向了原型對象 Person.prototype
,這是從實例尋找構造函數的惟一途徑。當咱們用字面量的方式重寫原型對象時,原型對象中的 constructor
會指向字面量建立對象的構造函數 Object
。這是咱們不但願看到的,咱們再手動把 constructor
的指向修正爲 Person
。
Person.prototype = {
constructor: Person,
name: 'Nicholas',
age: 27,
sayName: function () {
console.log(this.name);
};
}
複製代碼
按照這種方式使用原型模式就比較穩妥了。
歡迎你們指正、補充!