前言
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会有两种方式实现继承:一个是接口实现,一个是继承。而JS只是继承,不支持接口实现,JS实现继承的方式依靠原型链完成。
关于原型相关属性的含义:
- prototype:原型,只有构造函数才有的属性,指向该构造函数的原型对象。
- __proto__:原型链,只有实例对象才有的属性,指向new这个实例的构造函数的原型对象 。
- constructor:原型对象中的属性,指向该原型对象的构造函数。
关于原型方面的几句话:
- 当 new 一个函数的时候会创建出一个对象,所以【被创建对象.__proto__】等于【函数.prototype】。
- 一切函数都是由 Function 这个函数创建的,所以【被创建的函数.__proto__】等于【Function.prototype】。
- 一切函数的原型对象都是由 Object 这个函数创建的,所以【一切函数.prototype.__proto__】等于【Object.prototype】。
在ES6中,我们使用 extends 来实现类的继承,继承后,子类具有超类的所有属性和方法,也包括超类的静态属性和静态方法。
class Box{
constructor(){}
play(){}
}
//Ball继承了Box的所有属性和方法
class Ball extends Box{
constructor(){
//调用超类的构造函数
super();
}
}
ES5中,在给构造函数设置原型属性和方法时,尽量避免采用对象的形式来写,因为这样会覆盖该函数原来的 prototype 内容。
function Box(_r){
this.r=_r;
}
Box.prototype.b=10;
Box.prototype.play=function(){
console.log(this.b);
}
console.dir(Box);//打印如果如下
如果采用对象的方式来设置 prototype时:
function Box(_r) {
this.r = _r;
}
Box.prototype={
b:10,
play:function(){}
}
console.dir(Box);//打印如果如下
我们会发现打印结果的 prototype 中少了constructor 属性;
- 如果是基类,也可以直接这样写,但是后面还必须重新定义constructor为当前类的构造函数,要注意这个属性不可修改、不可删除、不可被遍历。
- 如果不是基类,就不能直接使用 prototype 重设置对象。
Object.defineProperty(Box.prototype,"constructor",{
value:Box
})
现在我们来看ES5中继承是怎样实现的。
组合式继承
原型链继承的基本思路就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
原型链+借用构造函数的模式,这种模式称为组合继承。
function Box(_r){
this.r=_r;
console.log("aa");
}
Box.a=3;
Box.run=function(){console.log(Box.a);}
Box.prototype.b=10;
Box.prototype.play=function(){console.log(this.b);}
function Ball(_r){
Box.call(this,_r);//冒充,给超类传参
}
//实例对象的原型链就是类的原型
Ball.prototype=new Box(3);
var c=new Ball(10);
console.log(c);//打印结果如下
可以看到超类的构造函数被执行两遍,所以这种继承方式是有问题的,这种继承只适用于超类的构造函数中没有任何语句的情况。
原型继承
这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function Box(_r){
this.r=_r;
console.log("aa");
}
Box.a=3;
Box.run=function(){console.log(Box.a);}
Box.prototype.b=10;
Box.prototype.play=function(){console.log(this.b);}
function F(){ }
F.prototype=Box.prototype;
function Ball(){ }
Ball.prototype=new F();
var b=new Ball();
console.log(b);//打印结果如下
对比组合式继承来讲,这种方式就不用执行两遍超类的构造函数,但是一遍都没执行,只完成了原型的继承。
Object.create
ES5提供了另外一种简便的集成方法Object.create(),因为该方法在创建对象的同时会将其所有原型链传入,因此,继承后不会出现二次调用基类构造函数的情况。
子类继承的时候,给原型链赋值为Object.create(父类.原型链),Object.create()会创建一个对象,并关联到参数对象中,避免了new操作符与生成对应的constructor,prototype。
function Box(_r){
this.r=_r;
console.log("aa");
}
Box.a=3;
Box.run=function(){console.log(Box.a);}
Box.prototype.b=10;
Box.prototype.play=function(){console.log(this.b);}
function Ball(_r){
Box.call(this,_r);//冒充,给超类传参
}
Ball.prototype=Object.create(Box.prototype);
var b=new Ball(10);
console.log(b);//打印结果如下
这种继承写法中,执行一遍超类的构造函数,也完成了原型的继承。但是只能用于IE9以上浏览器,如果这样的话,直接使用ES6 类的继承更方便。
寄生式继承
寄生式继承把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。
function Box(_r){
this.r=_r;
console.log("aa");
}
Box.a=3;
Box.run=function(){console.log(Box.a);}
Box.prototype.b=10;
Box.prototype.play=function(){console.log(this.b);}
function Ball(_r) {
//执行超类的构造函数
this.superClass.apply(this, arguments);
}
Ball.prototype.walk = function () {
console.log("walk");
}
//寄生式继承
extend_1(Ball,Box);
Ball.prototype.play = function () {
//执行超类的play方法
this.superClass.prototype.play.apply(this,arguments);
}
var b = new Ball(10);
console.log(b);//打印结果如下
function extend_1(subClass,supClass) {
//函数F类似于一个中间变量
function F() {}
//将超类的prototype赋值给中间变量
F.prototype = supClass.prototype;
//先把子类的prototype存储起来
var o = subClass.prototype;
//子类的prototype继承超类
subClass.prototype = new F();
//再将子类原来的prototype添加回来
for (var prop in o) {
subClass.prototype[prop] = o[prop]
}
//将超类存储在子类的原型上,用来调用超类的函数
subClass.prototype.superClass = supClass;
//设置子类的构造函数是它自己
subClass.prototype.constructor = subClass;
//如果在写超类的时候直接设置它的原型是一个对象。这样它的构造函数就会被覆盖掉。
//在子类继承超类的时候。看看超类是不是这个被覆盖掉了,如果被覆盖掉了,就让它的构造函数恢复。
if (supClass.prototype.constructor === Object) {
supClass.prototype.constructor = supClass;
}
}
可以将它写成函数的一个方法,直接调用。
Function.prototype.extend_1 = function (supClass) {
function F() { }
F.prototype = supClass.prototype;
var o = this.prototype;
this.prototype = new F();
for (var prop in o) {
this.prototype[prop] = o[prop]
}
this.prototype.superClass = supClass
this.prototype.constructor = this;
if (supClass.prototype.constructor === Object) {
supClass.prototype.constructor = supClass;
}
}
//使用方法 子类.extend_1(超类)
Ball.extend_1(Box);