JavaScript 原型继承

JavaScript 中虽然有对象的概念,但它并不是一门严格意义上的面向对象编程的语言。

尽管 ES6 引入了 class 关键字,但是本质上仍然是对原型链的操作。

通过修改 JavaScript 的原型,可以实现类之间的继承关系。

首先用 function 关键字定义一个 ParentClass

 1 function ParentClass(props) {
 2     this.alpha   = props.alpha || 1.0;
 3     this.color   = props.color || [0.8, 0.8, 0.8];
 4     console.log("ParentClass constructor");
 5 };
 6 
 7 ParentClass.prototype = {
 8     constructor: ParentClass,
 9 
10     init: function(gl) {
11         console.log("ParentClass.proptotype.init");
12     },
13 
14     paint: function(gl, addon) {
15         console.log("ParentClass.proptotype.paint");
16     }
17 }

并且在 ParentClass 的原型上定义了 init 和 paint 函数。注意这里的构造器仍然是 ParentClass。

再用 function 定义 ChildClass

1 function ChildClass(props) {
2     ParentClass.call(this, props);
3     this.sides = props.sides || 24;
4     console.log("ChildClass constructor");
5 }

这里的 ParentClass 的 call 函数相当于在 ChildClass 继承了 ParentClass 之后调用 super 函数,也就是调用父类 ParentClass 的构造器。

当然这里还没有让  ChildClass 继承 ParentClass,如果调用 ChildClass 的 init 方法就会报错。

在调用 ChildClass 对象的方法时,首先就会在 ChildClass 的原型上查找是否有同名的方法,如果找不到方法,就会到原型链的上一层查找,直到 Object,

如果这个时候仍然找不到对应名称的方法,就会报错了。原型链过长的话就会影响运行速度。

要做到对象间的继承,就要修改 ChildClass 的原型链,把 ChildClass 的原型链指向 ParentClass。

这里直接给出廖雪峰的继承代码

1 /**
2   * this function is copied from liaoxuefeng's javascript tutorial
3 **/
4 inherits = function(child, parent) {
5     var F = function() {};
6     F.prototype = parent.prototype;
7     child.prototype = new F();
8     child.prototype.constructor = child;
9 }

通过一个中间函数 F,把它的原型链指向 parent 的父类,再把子类的原型改为 F 函数 new 出来的对象。child 的构造器当然还应该是 child 自己。

注意这里的 child 和 parent 应该都是指向 function 的对象(而不是用 new 关键字创造的对象)

1 inherits(ChildClass, ParentClass);  // ChildClass 继承 ParentClass

创建子类的对象

1 var child = new ChildClass({}); 

那么就会输出两行内容,分别是:

ParentClass constructor
ChildClass constructor 

再看看子类中相应的属性或方法:

1 console.log(child.alpha);  // 1
2 
3 child.paint();  // ParentClass.prototype.paint

默认的 alpha 值是1,paint 继承于 ParentClass,所以分别输出 1 和 ParentClass.prototype.paint

要重写 ChildClass 的 paint 方法,直接修改原型上的 paint 属性,指向别的函数就好了:

1 ChildClass.prototype.paint = function() { console.log("ChildClass.prototpye.paint")};
2 child.paint();     // ChildClass.prototype.paint
3 ChildClass.paint;  // undefined

再调用 child.paint(),输出的就是 ChildClass.prototype.paint

关于 ChildClass 原型上的属性,只有在 new 出了对象后,由对象调用,而 ChildClass 本身是没有 paint 属性的。

再看一个例子:

1 ChildClass.testPaint = function() {console.log("ChildClass.testPaint")};
2 child = new ChildClass({});
3 child.testPaint  // undefined

如果直接给 ChildClass 添加 testPaint 属性(方法),new 出来的对象不能访问相应的属性(方法)。

那么就可以这样理解,ChildClass 原型上的属性可以被 new 出来的对象访问,相当于 Java 中类中的普通方法;

而直接在 ChildClass 上添加属性,只能被 ChildClass 访问,而不能被 new 出来的对象访问,相当于 Java 中类的静态方法。

另外,我尝试了下直接将子类(姑且这么叫吧)的原型直接指向父类的对象中,好像没有问题:

1 function SecChildClass(props) {
2     ParentClass.call(this, props);
3     this.sides = props.sides || 24;
4     console.log("SecChildClass constructor");
5 }
6 
7 SecChildClass.prototype = new ParentClass({});  //  ParentClass constructor
8 SecChildClass.constructor = SecChildClass;

当然在修改 SecChildClass 的原型链,指向 ParentClass 的对象时,就会执行一遍 ParentClass 的构造器,所以这个方式确实并不好。

再看看相应对象的属性

1 var c2 = new SecChildClass({});
2 //  ParentClass constructor
3 //  SecChildClass constructor
4 
5 c2.alpha;   // 1
6 c2.paint();  // ParentClass.prototype.paint

创造对象时就会输出两行内容,因为在子类的构造器里首先调用的是父类的 call 方法,所以首先执行的是父类的构造器。

值得一提的是,如果在子类的构造器中就调用 inherits 函数,传入 this 是没有用的,因为 this 指向的是子类的对象而非 ChildClass,这样的继承是没有效果的。

reference

廖雪峰 JavaScript 教程

本博客由 BriFuture 原创,并在个人博客(WordPress构建) BriFuture's Blog 上发布。欢迎访问。
欢迎遵照 CC-BY-NC-SA 协议规定转载,请在正文中标注并保留本人信息。
原文地址:https://www.cnblogs.com/brifuture/p/8288616.html