javascript中继承方式及优缺点(一)

分别介绍原型链继承、call/apply继承(借用构造函数继承)、组合继承、原型式继承、寄生式继承、寄生组合式继承

1. 原型链继承


 

核心:将父类的实例作为子类的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
};
 
function SubType(){
  this.subproperty = false;
}
// 继承自SuperType
SubType.prototype = new SuperType();
 
SubType.prototype.getSubValue = function (){
  return this.subproperty;
};
 
var instance = new SubType();
alert(instance.getSuperValue());//true

简单的原型链分析

 

使用原型创建对象会存在多个实例对引用类型的操作会被篡改的问题,在上面同样存在这个问题,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(){
  this.colors = ["red", "blue", "green"];
}
function SubType(){}
 
SubType.prototype = new SuperType();
 
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
 
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

两个实例对象instance1和instance2的colors属性指向相同,改变一个会影响另一个实例的属性

缺陷:
(1)原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
(2)不能传递参数

2. 借用构造函数继承


 

核心:使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
 
function SubType(name, age){
  // 继承自SuperType
  SuperType.call(this, name);
   
  this.age = age;
}
 
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
 
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"
 
alert(instance1.name); // "Nicholas"
alert(instance1.age); // 29

借用构造函数继承的核心就在于SuperType.call(this, name),“借调”了SuperType构造函数,这样,SubType的每个实例都会将SuperType中的属性复制一份

缺陷:
(1)只能继承父类的实例属性和方法,不能继承原型属性/方法
(2)无法实现复用,每个子类都有父类实例函数的副本,影响性能

3. 组合继承


 

核心:结合原型链继承和构造函数继承通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};
 
function SubType(name, age){
  //继承属性
  SuperType.call(this, name);
  this.age = age;
}
 
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};
 
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
 
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

图解:

 

缺陷:
父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法

这个方法是javascript中最常用的继承模式

4. 原型式继承


 

核心:直接将某个对象直接赋值给构造函数的原型

1
2
3
4
5
function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}

object()对传入其中的对象执行了一次浅复制,将F的原型直接指向传入的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
 
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
 
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
 
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

缺点:
(1)原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
(2)无法传递参数

另外,ES5中存在Object.create()的方法,能够代替上面的object方法

5. 寄生式继承


 

核心:在原型式继承的基础上,增强对象,返回构造函数

1
2
3
4
5
6
7
function createAnother(original){
  varclone=object(original); // 过调用函数创建一个新对象
  clone.sayHi = function(){ // 以某种方式增强这个对象
    alert("hi");
  };
  return clone; // 返回对象
}

函数的主要作用是为构造函数新增属性和方法,以增强函数

1
2
3
4
5
6
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

缺点:
(1)原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
(2)无法传递参数

6. 寄生组合式继承


 

核心:结合借用构造函数传递参数和寄生模式实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype); //创建对象
  prototype.constructor = subType;                    // 增强对象
  subType.prototype = prototype;                      // 指定对象
}
 
// 父类初始化实例属性和原型属性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};
 
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}
 
// 将父类原型指向子类
inheritPrototype(SubType, SuperType);
 
// 新增子类原型属性
SubType.prototype.sayAge = function(){
  alert(this.age);
}
 
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
 
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]

图解:

寄生组合继承集合了前面几种继承优点,几乎避免了上面继承方式的所有缺陷,是执行效率最高也是应用面最广的,就是实现的过程相对繁琐。

 

posted @   小旭的blog  阅读(187)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示