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