首頁  >  文章  >  web前端  >  十分鐘帶你了解JavaScript的繼承

十分鐘帶你了解JavaScript的繼承

WBOY
WBOY轉載
2022-01-18 17:47:091204瀏覽

這篇文章為大家帶來了JavaScript中繼承的相關知識,其中包括了原型鏈繼承、借用構造函數繼承、組合繼承和多重繼承,希望對大家有幫助。

十分鐘帶你了解JavaScript的繼承

原型鏈繼承

原則

本質是重寫原型對象,代之以一個新類型的實例。在下面程式碼中,原來存在於SuperType的實例物件的屬性和方法,現在也存在於SubType.prototype中了。

實作

function Super(){
    this.value = true;
}
Super.prototype.getValue = function(){
    return this.value
}
function Sub(){};
// Sub继承了Super
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
const ins = new Sub();
console.log(ins.getValue()); // true

Sub繼承了Super,而繼承是透過建立Super實例,並將實例賦給Sub.prototype實現的。原來存在於Super的實例中的所有屬性和方法,現在也存在與Sub.prototype中。如圖所示。

十分鐘帶你了解JavaScript的繼承

上圖可以看出,沒有使用Sub預設提供的原型,而是給它換了一個新原型;這個新原型就是Super的實例。於是,新原型不僅有作為一個Super的實例所擁有的屬性和方法,而且它也指向了Super的原型。最終結果就是這樣的:

ins=>Sub的原型=>Super的原型

getValue()方法仍然還在Sub.prototype中,但value屬性則位於Sub.prototype中。這是因為value是一個實例屬性,而getValue()則是原型方法。既然Sub.prototype現在是Super的實例,那麼value就位於該實例中。

此外,要注意ins.constructor現在指向的是 Super,這是因為原來 Sub.prototype 中的 constructor 被重寫了的緣故。

缺點

  • 私人原型屬性會被實例共用

  • 在建立子類型的實例時,不能傳遞參數給父類型的建構子

原型鏈最主要的問題:私有原型屬性會被實例共享,而這正是為什麼要在建構子中,而不是原型物件中定義屬性的原因。透過原型來實現繼承時,原型實例會變成另一個類別的實例。於是,原先的實例屬性也就順理成章的變成了現在的原型屬性了。

function Super(){
    this.colors = ['red','green','blue'];
}
Super.prototype.getValue = function(){
    return this.colors
}
function Sub(){};
//Sub继承了Super
Sub.prototype = new Super();
const ins1 = new Super();
ins1.colors.push('black');
console.log(ins1.colors);//['red','green','blue','black'];
const ins2 = new Sub();
console.log(ins2.colors);//['red','green','blue','black'];

原型鏈的第二個問題,在建立子類型的實例時,不能傳遞參數給父類型的建構子。實際上,應該說是沒有辦法在不影響所有都想實例的情況下,傳遞參數給父類型的建構子參數。再加上包含引用類型值的原型屬性會被所有實例共享的問題,在實踐中很少會單獨使用原型鏈繼承

#注意問題

##使用原型鏈繼承方法要謹慎地定義方法,子類型有時候需要重寫父類別的某個方法,或是需要加入父類別中不存在的某個方法。但不管怎樣,給原型添加方法的程式碼一定要放在替換原型的語句之後。

function Super() {
    this.colors = ['red', 'green', 'blue'];
}
Super.prototype.getValue = function() {
    return this.colors
}
function Sub() {
    this.colors = ['black'];
};
//Sub继承了Super
Sub.prototype = new Super();
//添加父类已存在的方法,会重写父类的方法
Sub.prototype.getValue = function() {
    return this.colors;
}
//添加父类不存在的方法
Sub.prototype.getSubValue = function(){
    return false;
}
const ins = new Sub();
//重写父类的方法之后得到的结果
console.log(ins.getValue()); //['black']
//在子类中新定义的方法得到的结果
console.log(ins.getSubValue());//false
//父类调用getValue()方法还是原来的值
console.log(new Super().getValue());//['red', 'green', 'blue']

借用建構子繼承

#原理

借用建構子(有時也叫做偽類繼承或經典繼承)。這種技術的基本思想相當簡單,即在子類別建構函數的內部呼叫父類別建構子。別忘了,函數只不過是在特定環境中執行程式碼的對象,因此透過使用apply()和call()方法也可以在新建立的對像上執行建構子。

實作

function Super() {
    this.colors = ['red', 'green', 'blue'];
}
Super.prototype.getValue = function(){
    return this.colors;
}
function Sub(){
//继承了Super
Super.call(this);//相当于把构造函数Super中的this替换成了ins实例对象,这样在Super只有定义的私有属性会被继承下来,原型属性中定义的公共方法不会被继承下来
}
const ins = new Sub();
console.log(ins.colors);

傳遞參數:相對於原型鏈來講,借用建構函式繼承有一個很大的優勢,就是可以在子類別建構子中向父類別建構子傳遞參數

function B(name){
    this.name = name;
}
function A(){
    //继承了B,同时还传递了参数
    B.call(this,'ZZ');
    //实例属性
    this.age = 100;
}
const p = new A();
alert(p.name);//'ZZ'
alert(p.age);//100

缺點

如果只是藉用建構函數,那麼將無法避免建構子模式存在的問題-方法都在建構函數中定義,因此函數重複使用就無從談起。而且,在父類別的原型中定義的方法,對子類別而言是不可見的,所以這種方式使用較少。

組合繼承

原理

組合繼承,指的是將原型鍊和借用構造函數技術組合在一起,從而發揮兩者之長的一種繼承模式。背後的想法是使用原型鏈實現對原型上的公共屬性和方法的繼承,而藉由借用建構函式繼承來實現父類別私有屬性的繼承。這樣,也就是透過在父類別原型上定義方法實作了函數重複使用,又能夠確保每個實例都有父類別的私有屬性。

實作

function Super(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    Super.call(this,name);
    this.age = age;
}
// 继承方法
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    alert(this.age);
}
const ins = new Sub('jarvis',18);
ins.colors.push('black');
console.log(ins.colors);// ["red", "blue", "green", "black"]
ins.sayName();//'jarvis'
ins.sayAge();//18
const ins2 = new Sub('ershiyi',21);
console.log(ins2.colors);//["red", "blue", "green"]
ins2.sayName();//'ershiyi'
ins2.sayAge();//21

在上个例子中,Sub构造函数定义了两个属性:name和age。Super的原型定义了一个sayName()方法。在Sub构造函数中调用Super构造函数时传入了name参数,紧接着又定义它自己的属性age。然后,将Super的实例赋值给Sub的原型,然后又在该新原型上定义了方法sayAge()。这样一来,就可以让不同的Sub实例分别拥有自己的属性——包括colors属性,又可以使用相同的方法组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,称为JavaScript中最常用的继承模式。

缺点

无论在什么情况下,都会调用两次父类的构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。

寄生组合式继承

原理

组合继承是JavaScript最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但不得不在调用子类型构造函数时重写这些属性。再来看一看下面组合继承的例子。

实现

function Super(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    Super.call(this,name);
    this.age = age;
}
// 继承方法
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    alert(this.age);
}
const ins = new Sub('jarvis',18);
ins.colors.push('black');
console.log(ins.colors);// ["red", "blue", "green", "black"]
ins.sayName();//'jarvis'
ins.sayAge();//18
const ins2 = new Sub('ershiyi',21);
console.log(ins2.colors);//["red", "blue", "green"]
ins2.sayName();//'ershiyi'
ins2.sayAge();//21

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背 后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示。

function Super(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    //继承实例属性
    Super.call(this,name);
    this.age = age;
}
// 继承公有的方法
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    alert(this.age);
}
const ins = new Sub('jarvis',18);
ins.colors.push('black');
console.log(ins.colors);// ["red", "blue", "green", "black"]
ins.sayName();//'jarvis'
ins.sayAge();//18
const ins2 = new Sub('ershiyi',21);
console.log(ins2.colors);//["red", "blue", "green"]
ins2.sayName();//'ershiyi'
ins2.sayAge();//21

多重继承

JavaScript中不存在多重继承,那也就意味着一个对象不能同时继承多个对象,但是可以通过变通方法来实现。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>18 多重继承</title>
</head>
<body>
<script type="text/javascript">
// 多重继承:一个对象同时继承多个对象
// Person  Parent  Me
function Person(){
this.name = &#39;Person&#39;;
}
Person.prototype.sayName = function(){
console.log(this.name);
}
// 定制Parent
function Parent(){
this.age = 30;
}
Parent.prototype.sayAge = function(){
console.log(this.age);
}
function Me(){
// 继承Person的属性
Person.call(this);
Parent.call(this);
}
// 继承Person的方法
Me.prototype = Object.create(Person.prototype);
// 不能重写原型对象来实现 另一个对象的继承
// Me.prototype = Object.create(Parent.prototype);
// Object.assign(targetObj,copyObj)
Object.assign(Me.prototype,Parent.prototype);
// 指定构造函数
Me.prototype.constructor = Me;
const me = new Me();
</script>
</body>
</html>

ES5 与 ES6 继承差异

在 ES5 的传统继承中, this 的值会先被派生类创建,随后基类构造器才被调用。这意味着 this 一开始就是派生类的实例,之

后才使用了基类的附加属性对其进行了装饰。

在 ES6 基于类的继承中, this 的值会先被基类创建,随后才被派生类的构造 器所修改。结果是 this 初始就拥有作为基类的内置对象的所有功能,并能正确接收与之关联的所有功能。

【相关推荐:javascript学习教程

以上是十分鐘帶你了解JavaScript的繼承的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除