原型鏈詳解

建立普通對象

咱們知道,建立對象能夠這樣javascript

var person = new Object();
person.name = 「Nicholas」;
person.age = 29;
person.job = 「Software Engineer」;
person.sayName = function(){ alert(this.name);};
複製代碼

這個對象有name, age, job屬性,還有一個sayName()的方法。這是你們經常使用的建立對象的方法,能夠不斷添加新屬性,可是若是要建立多個相似的對象的話,就須要不斷地重複這段代碼,耗費不少內存。java

工廠模式

這時候能夠用工廠模式來解決這個問題bash

function createPerson(name, age, job){ 
    var o = new Object(); //1.建立一個空對象並賦值給變量o
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
    alert(this.name);
    	};
	return o; //2.返回變量o
}
var person1 = createPerson(「Nicholas」, 29, 「Software Engineer」); 
var person2 = createPerson(「Greg」, 27, 「Doctor」);
複製代碼

工廠模式還能夠更加簡練,好比能夠建立一個語法糖,省略掉上一段代碼的1和2 這時候便涉及到了Constructor模式了ide

Constructor模式

function Person(name, age, job){ 
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){ 
        alert(this.name);
    };
}
var person1 = new Person(「Nicholas」, 29, 「Software Engineer」); 
var person2 = new Person(「Greg」, 27, 「Doctor」);
複製代碼

注意constructor老是以大寫開頭,這是它的書寫規範。 相比工廠模式,當咱們用new這個關鍵字的時候,它幫咱們寫了一下步驟:函數

  • 建立一個新對象
  • 將這個對象的__proto__屬性綁定到原型上面
  • 將這個新對象賦給當前上下文(也就是this)
  • 自動返回this 使用contructor模式還有一個好處,能夠幫你判斷類型
alert(person1 instanceof Person); //true
複製代碼

一樣的,contructor模式也存在一個問題,在上一段代碼中,咱們添加了一個sayName()的方法優化

this.sayName = function(){
    alert(this.name);
    	};
複製代碼

若是須要建立100個不一樣的person, 也須要建立100個sayName()函數.ui

alert(person1.sayName == person2.sayName); //false
複製代碼

可是這個函數代碼都是同樣的,實際上只須要建立一次便可。this

function createPerson(name, age, job){ 
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
    	};
var person1 = createPerson('Nicholas', 29, 'Software Engineer'); 
var person2 = createPerson('Greg', 27, 'Doctor');
複製代碼

若是要建立不少不一樣的對象,就必須建立不少不一樣的全局方法,很容易產生衝突,這時候另外一種方式就大顯身手了(原型)spa

prototype

function Person(){ }
Person.prototype.name = 「Nicholas」; 
Person.prototype.age = 29; 
Person.prototype.job = 「Software Engineer」; 
Person.prototype.sayName = function(){
    alert(this.name); 
};
var person1 = new Person();
person1.sayName(); //」Nicholas」
var person2 = new Person(); 
person2.sayName(); //」Nicholas」
alert(person1.sayName == person2.sayName); //true
複製代碼

每一個函數建立的時候,它的prototype屬性也會被建立,指向一個普通的對象,這個對象會有一個constructor屬性,指回這個函數,上面那段代碼中 Person.prototype.constrctor會從新指向Person。而每次建立一個新的實例,實例的__proto__屬性會指向這個ProtoTypeprototype

當調用 person1.sayName()的時候,它自己是沒有這個方法的,因此會自動到 person1.__proto__去找,若是尚未,就再上一層,去 person1.__proto__.__proto__找,直到找到或者返回null爲止。 讀取 person1的其餘屬性也是同樣的,若是對象自己沒有這個屬性,會到它的___proto__去找,也就是 Person.prototype,若是有的話,就返回這個值,沒有的話,會再到 Person.prototype.__proto__找,因爲 Person.prototype是個普通的對象實例,因此 Person.prototype.__proto__指向的是 Object.prototype

判斷一個屬性是否屬於自身的對象仍是對象的原型,能夠用hasOwnProperty()判斷

原型一樣能夠用下列代碼的方式建立,可是會覆蓋掉原來默認的Prototype, 也就沒有默認的constructor屬性。

function Person(){ }
Person.prototype = {
    name : 「Nicholas」,
    age : 29,
    job : 「Software Engineer」, 
    sayName : function () {
    alert(this.name); }
};
複製代碼

下面這段代碼也會運行錯誤,緣由是噹噹實例被建立的時候,它的__proto__已經指向了默認的Person.prototype,這個時候你將Person.prototype指向另外一個地址,實例的__proto__是沒辦法訪問到的

function Person(){ }
var person = new Person();
Person.prototype = {
    constructor: Person, 
    name : 「Nicholas」, 
    age : 29,
    job : 「Software Engineer」, 
    sayName : function () {
    alert(this.name); }
};
person.sayName(); //error
複製代碼

__proto__和prototype有什麼區別呢,__proto__是在新的實例對象生成的時候,自動綁定到構造函數的prototype上面的,全部的對象都有__proto__屬性。prototype則是在建立函數的時候,它的prototype屬性也會被建立,指向一個普通的對象,這個對象會有一個constructor屬性,指回這個函數。prototype只有函數纔有的屬性。

常見的建立對象的方式爲下列代碼,這樣避免了共享對象的衝突

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = [「Shelby」, 「Court」];
}
Person.prototype = {
    constructor: Person, 
    sayName : function () {
        alert(this.name); 
    }
};
var person1 = new Person(「Nicholas」, 29, 「Software Engineer」); 
var person2 = new Person(「Greg」, 27, 「Doctor」);
person1.friends.push(「Van」);
alert(person1.friends); //」Shelby,Court,Van」 
alert(person2.friends); //」Shelby,Court」 
alert(person1.friends === person2.friends); //false 
alert(person1.sayName === person2.sayName); //true
複製代碼

原型繼承

  1. 構造函數繼承

缺點:每次建立一個子實例,父方法會重複構建屢次,而且不會繼承父類的prototype

function SuperType(){ 
    this.property = true;
    this.run = function() {}
}
function SubType(){ 
    SuperType.call(this)
}
var instance = new SubType(); 
複製代碼
  1. 類式繼承

缺點:一個子類的實例更改子類原型從父類構造函數中繼承來的共有屬性就會直接影響到其餘子類

function SuperType(){ 
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){ 
    return this.property;
};
function SubType(){ 
    this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){ 
    return this.subproperty;
};
var instance = new SubType(); 
alert(instance.getSuperValue()); //true
複製代碼

經過SubType.prototype = new SuperType();完成了原型的繼承。爲何要用SuperType的實例來綁定SubTypeprototype呢,由於這個實例的__proto__屬性會指向SuperType.prototype, 經過這個方式來繼承SuperType的屬性和方法

若是不手動綁定構造函數的prototype, 它也會自動生成默認的prototype, 生成的protoype是一個普通對象,這個對象的constructor屬性指回構造函數,__proto__屬性指向Object的prototype, 由於這個對象也是Object的實例,而實例的__proto__指向構造函數的prototype,這個構造函數就是Object. 因此即便咱們不定義 toString()valueOf()等函數,也能夠直接調用,由於這些函數已經在Object裏面定義好了

若是要添加SubType的方法能夠

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){ 
    return this.property;
};
function SubType(){ 
    this.subproperty = false;
}
//inherit from SuperType 
SubType.prototype = new SuperType();
//new method
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
//override existing method
SubType.prototype.getSuperValue = function (){
    return false;
};
// 下面的方式是錯的,SubType.prototype被從新賦值,不可以再訪問父函數的方法了
<!--SubType.prototype = {-->
<!-- getSubValue: function (){--> <!-- return this.subproperty;--> <!-- }--> <!--}--> var instance = new SubType(); alert(instance.getSuperValue()); //false 複製代碼

原型鏈存在的問題

function SuperType(){
    this.colors = [「red」, 「blue」, 「green」];
}
function SubType(){ }
//inherit from SuperType 
SubType.prototype = new SuperType();
var instance1 = new SubType(); instance1.colors.push(「black」);
alert(instance1.colors); //」red,blue,green,black」
var instance2 = new SubType(); 
alert(instance2.colors); //」red,blue,green,black」
複製代碼

解決辦法

function SuperType(){
    this.colors = [「red」, 「blue」, 「green」];
}
function SubType(){ //inherit from SuperType 
    SuperType.call(this);
}
var instance1 = new SubType(); 
instance1.colors.push(「black」); 
alert(instance1.colors); //」red,blue,green,black」
var instance2 = new SubType();
alert(instance2.colors); //」red,blue,green」
複製代碼

可是上述方法也存在子對象沒法引用父對象prototype中的方法等問題

  1. 組合繼承
function SuperType(name){
    this.name = name;
    this.colors = [「red」, 「blue」, 「green」];
}
SuperType.prototype.sayName = function(){    
    alert(this.name);
};
function SubType(name, age){
 //inherit properties 
    SuperType.call(this, name);
    this.age = age; 
}
//inherit methods
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType(「Nicholas」, 29); instance1.colors.push(「black」); 
alert(instance1.colors); //」red,blue,green,black」
instance1.sayName(); //」Nicholas」;
instance1.sayAge(); //29
var instance2 = new SubType(「Greg」, 27); alert(instance2.colors); //」red,blue,green」
instance2.sayName(); //」Greg」; 
instance2.sayAge(); //27
複製代碼

上述方法會調用SuperType的constructor方法兩次

  1. 優化
function object(o){ 
    function F(){}
    F.prototype = o;
    return new F(); 
}

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); prototype.constructor = subType; 
    subType.prototype = prototype;
}

function SuperType(name){
    this.name = name;
    this.colors = [「red」, 「blue」, 「green」];
}
SuperType.prototype.sayName = function(){         
    alert(this.name);
};
function SubType(name, age){ 
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){ 
    alert(this.age);
};
複製代碼

或者

// Shape - 父類(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父類的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子類(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// 子類續承父類
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?',
  rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
  rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
複製代碼

使用Object.create()是將對象繼承到__proto__屬性上

相關文章
相關標籤/搜索