javascript代碼復用--繼承


由於javascript沒有類的概念,因此無法通過接口繼承,只能通過實現繼承。實現繼承是繼承實際的方法,javascript中主要是依靠原型鏈要實現。

原型鏈繼承

原型鏈繼承是基本的繼承模式,其本質是重寫原型對象,使其為新對象的實例。代碼實現如下:

function Person(){

    this.name = "default"; var temp = "temp";

}

Person.prototype.age=0;

Person.prototype.getName = function(){

    return this.name;

}

Person.prototype.getAge = function(){

    return this.age;

}

console.log(Person.prototype.age);//0

console.log(Person.age);//undefined

console.log(Person.prototype.name);//undefined

console.log(Person.name);//Person, if other property, should be undefined

function Student(){

    this.type = "student";

}

//inheritance

Student.prototype = new Person();

console.log(Student.prototype.constructor);//Person(){}

console.log(Student.prototype.name);//default

Student.prototype.constructor = Student;

var student1 = new Student();

console.log(student1.getName());//default

console.log(student1.name);//default

console.log(student1.getAge());//0

console.log(student1.age);//0

console.log(student1.__proto__.age);//0

console.log(student1.temp);//undefined

console.log(student1 instanceof Object);//true

console.log(student1 instanceof Person);//true

console.log(student1 instanceof Student);//true

console.log(Student instanceof Person);//false

 

以上代碼主要注意兩個問題:

1.函數局部變量,內部屬性及原型屬性的區別。var temp定義了一個局部變量,this.name定義了一個內部屬性,prototype.age則定義了一個原型屬性。

對於局部變量,無法在函數以外的地方調用,包括實例。

之前說過,函數本身的prototype屬性僅僅用於函數實例的屬性繼承,而函數本身不會使用這個關聯的prototype,在prototype中設置的屬性將直接作用於所有實例。(比如Person的實例Student.prototype和student1,注意Student並不是Person的實例

而對於函數內部屬性,函數實例將直接擁有對應的內部屬性(初始值),而無法通過函數本身使用內部屬性。這一點其實跟prototype屬性有所區別。

2.利用重寫原型對象實現繼承的時候,Student.prototype = new Person(), Student.prototype將指向了另一個對象Person.prototype,因此此時Student.prototype.constructor將指向Person函數。通過Student.prototype.constructor = Student 可以將其constructor重新指向Student。

通過原型鏈可以更好的理解上面的代碼:

clip_image001

 

原型鏈繼承的缺點

關於原型鏈繼承的問題,其實就是跟通過原型方式創建對象的問題一樣,就是原型中包含引用類型所帶來的共享問題。

還有就是創建實例的時候,無法向構造器中傳遞參數。

 

構造函數繼承

另一種經典的繼承便是通過構造函數實現繼承,即通過apply()和call()方法在子類構造函數內部調用父類構造函數。具體實現如下:

function Person(name){

    this.name = name;

    this.friends = new Array();

}

Person.prototype.age = 0;

function Student(name){

    Person.call(this, name);

}

var student1 = new Student("Huge");

student1.friends.push("Alan");

console.log(student1.name);//Huge

console.log(student1.age);//undefined

console.log(student1.friends);//["Alan"]

var student2 = new Student("Heri");

student2.friends.push("Amly");

console.log(student2.name);//Heri

console.log(student2.friends);//["Amly"]

console.log(student1 instanceof Person);//false

console.log(student1 instanceof Student);//true

 

通過構造函數繼承的問題除了構造函數模式本身存在的缺點之外(重復實例化方法),也無法類型識別,因此在父類原型中定義的方法和屬性無法在子類中調用。

 

組合繼承

由於通過原型鏈繼承和構造函數繼承都有其優缺點,因此將這兩種繼承方式組合起來,使用原型鏈繼承實現原型中方法和屬性的繼承,通過構造函數繼承實現參數傳遞和引用類型繼承,是javascript中最常用的繼承模式。代碼實現如下:

function Person(name, age){

    this.name = name;

    this.age = age;

    this.friends = new Array();

}

Person.prototype.getName = function(){

    return this.name;

}

function Student(name, age){

    this.type = "student";

    Person.call(this, name, age);

}

Student.prototype = new Person();

Student.prototype.constructor = Student;

var student1 = new Student("Huge", 15);

student1.friends.push("Alan");

console.log(student1.name);//Huge

console.log(student1.age);//15

console.log(student1.friends);//["Alan"]

console.log(student1.getName());//Huge

console.log(student1 instanceof Person);//true

console.log(student1 instanceof Student);//true

var student2 = new Student("Heri", 16);

student2.friends.push("Amly");

console.log(student2.name);//Heri

console.log(student2.age);//16

console.log(student2.friends);//["Amly"]

console.log(Student.prototype.name);//undefined

console.log(Student.prototype.friends);//[]

 

從代碼可以看出,組合繼承會調用兩次父類的構造函數:創建子類原型的時候和在子類構造函數內部調用。實際上,第一次創建子類原型的時候,子類已經包含了父類對象的全部實例屬性,因此當通過調用子類構造函數創建實例的時候,將會重寫這些屬性。即同時存在兩組屬性,一組在實例上,一組在子類原型中,如上代碼中Student.prototype.friends返回的空數組。這就是調用兩次父類構造函數的結果

 

其他繼承方式

Crockford曾經提出了prototypal inheritance以及與之結合的parasitic inheritance。通過原型創建基於原有對象的新對象,並為新對象增加功能。

//prototypal inhertance

function createObject(obj){

    function F(){}

    F.prototype = obj;

    return new F();

}

//parasitic inheritance

function enhanceObject(obj){

    var enhanceObj = createObject(obj);

    enhanceObj.getName = function(){

       return this.name;

    }

    return enhanceObj;

}

var person = {

    name : "Alan"

};

var person1 = enhanceObject(person);

console.log(person1.getName());//Alan

 

更進一步,為了避免組合繼承模式兩次調用父類構造函數的問題,可以利用parasitic inheritance來繼承父類的原型,再將其指定給子類的原型。如下代碼:

//prototypal inhertance

function createObject(obj){

    function F(){}
    
    F.prototype = obj;

    return new F();

}

//parasitic inheritance

function inheritPrototype(superObj, subObj){

    var obj = createObject(superObj.prototype);

    obj.constructor = subObj;

    subObj.prototype = obj;

}

function Person(name, age){

    this.name = name;

    this.age = age;

    this.friends = new Array();

}

Person.prototype.getName = function(){

    return this.name;

}

function Student(name, age){

    this.type = "student";

    Person.call(this, name, age);

}

inheritPrototype(Person, Student); var student1 = new Student("Huge", 15);

student1.friends.push("Alan");

console.log(student1.name);//Huge

console.log(student1.age);//15

console.log(student1.friends);//["Alan"]

console.log(student1.getName());//Huge

console.log(student1 instanceof Person);//true

console.log(student1 instanceof Student);//true

var student2 = new Student("Heri", 16);

student2.friends.push("Amly");

console.log(student2.name);//Heri

console.log(student2.age);//16

console.log(student2.friends);//["Amly"]\

console.log(Student.prototype.name);//undefined

console.log(Student.prototype.friends);//undefined

 

可以看出,子類只調用了父類一次構造函數,避免在子類原型中創建不必要的屬性。同時,原型鏈也保持不便,可以說是實現類型繼承的最有效方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM