理解JavaScript繼承

對於JavaScript的繼承和原型鏈,雖然以前本身看了書也聽了session,但仍是一直以爲雲裏霧裏,不由感嘆JavaScript真是一門神奇的語言。此次通過Sponsor的一對一輔導和本身回來後反覆思考,總算以爲把其中的精妙領悟一二了。session

 

1. JavaScript建立對象函數

在面嚮對象語言中,一般經過定義類而後再進行實例化來建立多個具備相同屬性和方法的對象。可是在JavaScript中並無類的概念,不過ECMAScript中的構造函數能夠用來建立特定類型的對象。所以,在JavaScript中能夠建立自定義的構造函數,而且經過new操做符來建立對象。this

在JavaScript中並無「指定的」構造函數類型,構造函數實質上也是函數,它和通常函數的區別只在於調用方式不一樣。只有當經過new操做符來調用的時候它才能夠做爲構造函數來建立對象實例,而且把構造函數的做用域賦給這個新對象(將this指向這個新對象)。若是沒有使用new來調用構造函數,那就是普通的函數調用,這個時候this指向的是window對象,這樣作會致使全部的屬性和方法被添加到全局,所以必定要注意命名構造函數時首字母大寫,而且永遠使用new來調用它。spa

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
   this.say = function() {     console.log("Hello");   } } var person1 = new Person("Mike", "male"); var person2 = new Person("Kate", "female");

這段代碼就定義了一個構造函數Person, 而且給它添加了name和gender屬性以及say方法。經過調用new操做符來建立了兩個Person的實例person1和person2.能夠經過代碼來驗證一下:prototype

person1 instanceof Person; //true;
person2 instanceof Person; //true;

而且person1和person2都分別具備了name,gender屬性,而且都被附上了構造對象時傳入的值。同時它們也都具備say方法。指針

不過經過比較能夠看出來,雖然這時person1和person2都具備say方法,但它們其實並非同一個Function的實例,也就是說當使用new來建立構造函數的實例時,每一個方法都在實例上從新被建立了一遍:code

person1.say == person2.say; //false

這樣的重複建立Function是沒有必要的,甚至在實例變多的時候形成一種浪費。爲此,咱們可使用構造函數的prototype屬性來解決問題。prototype原型對象是用來尋訪繼承特徵的地方,添加到prototype對象中的屬性和方法都會被構造函數建立的實例繼承,這時實例中的方法就都是指向原型對象中Function的引用了:對象

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}

Person.prototype.say = function() {
    console.log("Hello");
}

var person1 = new Person("Mike", "male");
var person2 = new Person("Kate", "female");

person1.say == person2.say //true

 

2. prototype, constructor, 和__proto__blog

構造函數,原型對象,實例的關係是:JavaScript中,每一個函數都有一個prototype屬性,這是一個指針,指向了這個函數的原型對象。而這個原型對象有一個constructor屬性,指向了該構造函數。每一個經過該構造函數建立的對象都包含一個指向原型對象的內部指針__proto__。繼承

用代碼表示它們的關係:

Person.prototype.constructor === Person;
person1.__proto__ === Person.prototype;
person2.__proto__ === Person.prototype;

 

3. 繼承的實現

JavaScript中使用原型鏈做爲實現繼承的主要方法。因爲對象實例擁有一個指向原型對象的指針,而當這個原型對象又等於另外一個類型的實例時,它也具備這個指向另外一類型原型對象的指針。所以經過指向原型對象的指針__proto__就能夠層層遞進的找到原型對象,這就是原型鏈。經過原型鏈來完成繼承:

function Teacher(title) {
    this.title = title;
}
Teacher.prototype = new Person();

var teacher = new Teacher("professor");

這時,咱們經過將Teacher的prototype原型對象指向Person的實例來完成了Teacher對Person的繼承。能夠看到Teacher的實例teacher具備了Person的屬性和方法。

可是若是隻是將構造函數的prototype原型對象指向另外一對象實例,發生的事情其實能夠概括爲:

Teacher.prototype instanceof Person //true
Teacher.prototype.constructor == Person //true
Teacher.prototype.__proto__ === Person.prototype //true

問題出現了:這時Teacher的構造函數變成了Person。雖然咱們在使用建立的實例的屬性和方法的時候constructor的類型並不會產生很大的影響,可是這依然是一個很不合理的地方。所以通常在使用原型鏈實現繼承時,在將prototype指向另外一個構造函數的實例以後須要再將當前構造函數的constructor改回來:

Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;

這樣纔是真正的實現了原型鏈繼承而且不改變當前構造函數和原型對象的關係:

到這裏,咱們就能夠將這個繼承過程封裝成一個extend方法來專門完成繼承的工做了:

var extend = function(Child, Parent) {
  Child.prototype = new Parent();
  Child.prototype.constructor = Child;
  return new Child();
};

如今這個方法接受兩個參數:Child和Parent,而且在完成繼承以後實例化一個Child對象並返回。咱們固然能夠根據須要來繼續豐富這個函數,包括實例化的時候須要傳入的參數什麼的。

相關文章
相關標籤/搜索