js继承

本文最后更新于:2022年3月22日 下午

原型链与继承

es5没有类的机制, 可以通过构造函数来充当类. 构造函数的属性和变量可以被实例继承.

1
2
3
4
5
let Human = function(name, age){
this.name = name;
this.age = age;
}
let mugu = new human();
  • 构造函数的形式每次实例化都会创建新的变量name, age.
  • 他们无法共享. 比如, 如果有个函数较sayFxxk(){}, 这个函数实际上是稳定的, 没有必要重复创建. 会造成系统资源的浪费

为此, 定义了prototype原型对象. 原型对象上的属性和方法都可以被共享.

  • js规定每个函数都有prototype属性, 指向一个对象.
  • 对于构造函数来说, 该属性会成为实例对象的原型.
1
2
3
Human.prototype.sayFxxk = function(){ alert("hi"); }
//上述方法mugu实例中并没有sayFxxk(). 但通过原型查找找到了该属性, 是共享的.
//mugu实例如果自己重写了sayFxxk(). 则不会用原型上的sayFxxk().

原型链

  • 所有对象都有自己的原型对象, 一个对象也可以充当其他对象的原型
  • 由于原型对象也是对象, 他也有自己的原型.
  • 所以这种机制下就会形成一条链, 我们叫它为原型链.
  • 原型链的顶端, 即Object构造函数的prototype. 所有对象都继承了它, 其中的共享方法如toString. valueOf. 再往上就是null了,
  • 也就是说, 当寻找一个属性的时候, js引擎会逐级往上, 直到找到或者到原型链顶端(遍历). 返回undefined.
1
Object.getPrototypeOf(Object.prototype) == null //true

由于js原型链的特点, 可以将一个对象设为原型即实现了继承, 缺点和优点一样明显, 如果是引用类型的值(比如一个数组), 修改的时候牵一发而动全身, 容易造成属性修改的混乱. 而且无法向超类传参.

1
2
3
4
5
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){};
Son.prototype = new Father();

经典继承(借用构造函数)

借用构造函数在基类中调用超类的构造方法.

既然不能直接使用超类或者传参, 那么在基类中调用超类的构造函数实例化一个就好了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
Father.call(this);//继承了Father,且向父类型传递参数
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"

var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的

//来自juejin.com

虽然解决了前面的问题, 但很明显, 又回到老路子:

  • 不能复用函数, 违背原型的初心.
  • 超类的方法, 基类不能访问(相当于father构造函数在son的内层作用域).

组合继承

用原型链实现原型方法的继承, 构造函数实现实例属性的继承.

那岂不是原型链不行, 构造函数也不行, 反观之, 我们主要目的是想要复用函数的同时一部分属性是唯一的. 那么, 原型链能复用函数, 构造函数能保证实例属性的唯一性. 那么两者结合起来就可以了, 这就是组合继承.

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
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);//继承实例属性,第一次调用Father()
this.age = age;
}
Son.prototype = new Father();//继承父类方法,第二次调用Father()
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5

var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10

缺点是, 调用了两次超类构造方法, 导致实例中存在不必要的超类的实例对象中的所有属性。

Object.create()

又叫原型式继承, 和原型链差不多, 它可以依据现有对象为原型, 生成新的对象

优点就是不用自己声明一个构造函数去设置原型, 缺点和原型链一样.

寄生式继承

封装继承的过程到一个构造函数中, 这样实现了对某个对象继承, 又拥有自己的属性和方法

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

很明显, 每次调用都会返回一个新对象, 同样不能复用方法.

寄生式组合继承

在继承原型时,我们继承的不是超类的实例对象,而是原型对象是超类原型对象的一个实例对象,这样就解决了基类的原型对象中增添了不必要的超类的实例对象中的所有属性d的问题。

我们不要多余的, 就要那个原型上的东西. 将这个过程封装下, 那么在组合继承里

  • 将要继承的超类构造函数, 换成超类的原型对象的构造函数.
  • 同时, 构造函数是基类
  • 最后将基类的原型指向构造好的prototype上.
1
2
3
4
5
function extend(subClass,superClass){
var prototype = object(superClass.prototype);//创建对象
prototype.constructor = subClass;//增强对象
subClass.prototype = prototype;//指定对象
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!