javascript:原型鏈繼承和構造函數繼承

原型鏈繼承

子類的全部實例都共享着原型上的全部屬性和方法。經過子類實例,能夠訪問原型上的屬性,可是,不能重寫原型上的屬性。javascript

//定義一個學生類
function Student(stuID, schoolName) {
    this.stuID = stuID;
    this.schoolName = schoolName;
}
//全部學生都有這樣一個特徵
Student.prototype.characteristic = '年輕有朝氣';
            
var stu1 = new Student(1001,'第一小學');
console.log(stu1.stuID);                //1001
console.log(stu1.characteristic);       //'年輕有朝氣'
            
//重寫characteristic
stu1.characteristic = '活潑可愛';
console.log(stu1.characteristic);       //'活潑可愛'

var stu2 = new Student(1002,'第一小學');
console.log(stu2.characteristic);       //'年輕有朝氣'
console.log(Student.prototype);         //{characteristic: "年輕有朝氣"}

上面這段代碼代表:
經過stu1.characteristic = '活潑可愛';並無改變原型上的屬性值。java

當實例中,存在和原型中同名的屬性時,會自動屏蔽原型上的同名屬性。stu1.characteristic = = '活潑可愛' 其實是給實例stu1添加了一個本地屬性 characteristic,因此當咱們再次訪問stu1.characteristic 時,訪問的是實例的本地屬性,而不是原型上的characteristic屬性(它因和本地屬性名同名已經被屏蔽了)。app

原型上的 characteristic 值是一個基本類型的值,若是是一個引用類型呢?這其中又會有一堆小九九。函數

其實原型上任何類型的值,都不會被實例所重寫。在實例上設置與原型上同名屬性的值,只會在實例上建立一個同名的本地屬性。可是,原型上引用類型的值能夠經過實例進行修改,並且全部的實例訪問到的該引用類型的值也會隨之改變。(不是很明白,既然引用類型的值都能被修改了,那麼爲何還說不會被實例重寫??難道修改!= 重寫)this

//定義一個學生類
function Student(stuID, schoolName) {
    this.stuID = stuID;
    this.schoolName = schoolName;
}
//全部學生都有這樣一個特徵
Student.prototype.characteristic = '年輕有朝氣';
Student.prototype.examItems = ['語文','數學','英語'];  //考試項目


var stu1 = new Student(1001, '第一小學');
console.log(stu1.examItems); //['語文','數學','英語']

//修改examItems
stu1.examItems.push('科學');
console.log(stu1.examItems); //['語文','數學','英語','科學']

var stu2 = new Student(1002, '第一小學');
console.log(stu2.examItems); //['語文','數學','英語','科學']

原型上任何類型的屬性值都不會經過實例被重寫,可是引用類型的屬性值會受到實例的影響而修改。prototype

構造函數繼承

在子類的構造函數中,經過 apply( ) 或 call( )的形式,調用父類構造函數,以實現繼承。code

//定義一個超類/父類: 人
function Person (name, age) {
    //人都有姓名,年齡,會吃飯,會睡覺
    //傳入出生年份 year,自動計算年齡
    this.name = name;
    this.age = age;
    this.eat = function () {
        alert('吃飯');
    }
    this.sleep = function () {
        alert('睡覺');
    }
}

//定義一個子類: 學生
//學生Student也是人,天然要繼承超類 Person 的全部屬性和方法
//學生都應當有姓名、年齡、會吃飯、會睡覺
//固然學生也有本身的一些屬性:學號,學校名稱等,和方法,好比都要去作一件事:寫做業
function Student (stuID, schoolName, name, age) {
    this.stuID = stuID;
    this.schoolName = schoolName;
    //用call調用 Person,以實現繼承
    Person.call(this, name, age);
}

Student.prototype.doHomework = function () {
    alert('作做業');
}

//實例化一個學生
var stu1 = new Student(1001, '第一小學', '王寶寶',20);
console.log(stu1.stuID);       //1001
console.log(stu1.schoolName);  //'第一小學'
console.log(stu1.name);        //'王寶寶'
console.log(stu1.age);         //20
stu1.eat();                    //'吃飯'
stu1.sleep();                  //'睡覺'
stu1.doHomework();             //'作做業'

在子類構造函數中,咱們經過 call 的方式調用了父類構造函數 Person實現了繼承。別忘了,函數只不過是一段能夠在特定做用域執行代碼的特殊對象,咱們能夠經過 call 方法指定函數的做用域。對象

在 stu1 = new Student() 構造函數時,Student 內部 this 的值指向的是 stu1, 因此 this.stuID =stu1.stuID, 因此 Person.call(this, name, age) 就至關於Person.call(stu1, '王寶寶', 20),就至關於 stu1.Person('王寶寶',20)。最後,stu1 去調用 Person 方法時,Person 內部的 this 指向就指向了 stu1。那麼Person 內部this 上的全部屬性和方法,都被拷貝到了stu1上。繼承

總之,在子類函數中,經過call() 方法調用父類函數後,子類實例 stu1, 能夠訪問到 Student 構造函數和 Person 構造函數裏的全部屬性和方法。這樣就實現了子類向父類的繼承。ip

缺點

這種形式的繼承,每一個子類實例都會拷貝一份父類構造函數中的方法,做爲實例本身的方法,好比 eat()。這樣作,有幾個缺點:

  1. 每一個實例都拷貝一份,佔用內存大,尤爲是方法過多的時候。

  2. 方法都做爲了實例本身的方法,當需求改變,要改動其中的一個方法時,以前全部的實例,他們的該方法都不能及時做出更新。只有後面的實例才能訪問到新方法。

因此,其實單獨使用原型鏈繼承或者借用構造函數繼承都有本身很大的缺點,最好的辦法是,將二者結合一塊兒使用,發揮各自的優點。

例如:

function OSTAccountDC() {
    OSTBaseDC.call(this);
}

OSTAccountDC.prototype = new OSTBaseDC(); //經過OSTBaseDC建立一個實例,避免改變父類構造函數的屬性,而只是在本地建立一個屬性。

OSTAccountDC.prototype.constructor = OSTAccountDC; //使this指向本身OSTAccountDC,而不是指向構造函數OSTBaseDC

OSTAccountDC.prototype.accountRequest = function(callback){
    // do something
}
相關文章
相關標籤/搜索