JavaScript原型鏈與繼承

最近學習了《Javascript高級程序設計》面向對象部分,結合書中的例子總結一下原型鏈和繼承部分的內容。函數

建立對象


在Js當中沒有類這個概念,當咱們想要建立具備相同屬性的對象的時候,有以下解決方法:學習

  • 工廠模式
  • 構造函數模式
  • 原型模式

其中,原型模式在Js中應用更加普遍,下面逐一對上述模式進行介紹。this

工廠模式

在ECMAScript中,所謂的工廠模式其實就用一個函數進行封裝,建立對象時傳入相應的參數便可。prototype

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}

var person1 = createPerson("Nick", 20, "Teacher");
var person2 = createPerson("Nancy", 21, "Doctor");

工廠模式一目瞭然,但它的缺點是,咱們沒法得知咱們建立的person1person2究竟屬於什麼類型,咱們只知道它們都是object, 但咱們但願更加具體。由於它們有相同的行爲,咱們但願person1person2都屬於一個叫Person的「類」。構造函數模式能夠知足這個要求。設計

構造函數模式

直接上代碼:指針

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    }
}

var person1 = new Person("Nick", 20, "Teacher");
var person2 = new Person("Nancy", 21, "Doctor");

下面解釋一下對象建立的過程,code

  1. 首先,new新建一個對象
  2. this指向這個新對象
  3. 執行Person函數中的代碼
  4. 返回這個新對象

如今,咱們能夠看一下person1person2的類型,對象

alert(person1 instanceof Object); // true
alert(person1 instanceof Person) // true

用構造函數建立對象也很方便,但有個缺點,注意上面例子中this.sayName方法,這種建立方式意味着咱們每建立一個新的Person實例,該實例內部都會新建一個sayName方法。而實際上,這些方法的做用都相同,沒有重複建立的必要。若是把這個函數放在構造函數以外,做爲全局函數的話,能夠解決重複的問題,但卻犧牲了Person的封裝性,所以咱們推薦下一種模式,原型模式。blog

原型模式

咱們須要一個「倉庫」存儲同一類型的對象的共有的屬性和方法,在js裏面,這個「倉庫」是prototype指向的對象(即__原型對象__)。繼承

咱們建立的每個函數都有一個prototype(原型)屬性,這個屬性指向「倉庫」(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();
peron2.sayName();   //'Nicholas'

alert(person1.sayName() == peron2.sayName());   //true

原型對象

原型對象是一個很重要的概念,它就是咱們上面提到的「倉庫」(可能比喻不是很恰當),先來理解一下它:
結合剛纔的代碼,有下圖:

有:

  1. 構造函數Person的prototype屬性指向Person prototype對象;Person Prototype的constructor又指向Person構造函數;
  2. 實例Person1和Person2的[[prototype]]指針也指向Person prototype對象;

在調用person1.sayName()的時候,解析器會先詢問person1中是否有sayName方法,發現沒有,就會查找person1的原型;在person1原型中發現有,就會使用原型中的sayName方法;

來看另一個例子:

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();
var person2 = new Person();

person1.name = "Gerg";

person1.sayName();  //'Gerg'
person2.sayName();   //'Nicholas'

在這裏,能夠看到設置了person1name屬性後,該屬性即存在於person1實例中,無需再從原型中查找,在person1中,至關於將原型的name屬性覆蓋。
person2不受影響。

語法簡化
上面的代碼有點冗長,能夠用對象字面量來重寫原型對象,代碼以下:

function Person() {
}

Person.prototype = {
    constructor : Person, 
    name : "Nicholas",
    age : 29,
    job : "software Engineer",
    sayName : function() {
        alert(this.name);
    }
}

原型的動態性

  1. 對原型的修改會直接反映到每一個實例,由於實例的[[prototype]] 其實是指向原型對象的指針;
  2. 若是直接重寫原型對象,原來的原型沒有被直接覆蓋,而是將構造函數的prototype指向新的原型對象。在此以前所建的實例的[[prototype]]不指向重寫後的原型對象。上代碼:
function Person() {
}

var friend = new Person();

Person.prototype = {
    constructor : Person, 
    name : "Nicholas",
    age : 29,
    job : "software Engineer",
    sayName : function() {
        alert(this.name);
    }
}
friend.sayName(); //error

從圖中能夠看出以前生成的實例的原型並無改變。

構造函數模式和原型模式結合

咱們經過構造函數的方式生成每一個實例獨自享有的屬性和方法,經過原型生成共享的屬性和方法:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friend = [shelby', 'Court'];
}

Person.prototype = {
    constructor : Person,
    sayName : function() {
        alert(this.name);
    }
}

var person1 = new Person('Nicholas', 29, 'software engineer');
var person2 = new Person('Gerg', 27, 'doctor');

person1.friends.push('Van');
alert(person1.friends);     //'shelby, Count, Van'
alert(person2.friends);     //'shelby, Count'
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true

繼承

未完待續...

相關文章
相關標籤/搜索