第六章 继承
6.1 原型链
看图理解
6.1.1 原型链示例
原型链是 Javascript 中实现继承的默认方式.
- // 首先创建三个构造器函数
- function Shape() {
- this.name = 'Shape';
- this.toString = function () {
- return this.name;
- }
- }
- function TwoDShape() {
- this.name = '2D shape'
- }
- function Triangle(side, height) {
- this.name = 'Triangle';
- this.side = side;
- this.height = height;
- this.getArea = function () {
- return this.side * this.height / 2
- }
- }
- // 继承原型
- TwoDShape.prototype = new Shape();// 这里用 Shape 创建了一个新的对象, 然后用它去覆盖 TwoDShape 构造器的 prototype 属性
- Triangle.prototype = new TwoDShape();// 这里同上, 这样可以确保在继承实现之后, 对构造器 (Shape()) 的任何修改都不会对另一个构造器 (TwoDShape()) 产生影响, 因为我们所继承的只是由该继承器所建的一个实体(new Shape());
- // 重写 prototype 之后, 要记得修改 constructor, 可以避免对对象的 constructor 属性产生副作用.
- TwoDShape.prototype.constructor = TwoDShape;
- Triangle.prototype.constructor = Triangle;
- // 创建一个 Triangle 对象
- var my = new Triangle(5, 10);
运行 my.getArea();
结果 25;
my.toString();
结果: Triangle
- // 过程分析: 首先会遍历 my 对象中的所有属性, 没找到 toString()方法, 然后再骈查看 my_proto_所指向的对象, 是由 new TwoDShape()所创建的实体, 结果还是没有找到, 它会继续检查该实体的_proto_属性, 此时, 该_proto_所指向的实体是由 new Shape()所创建的, 所以可以在 new Shape 所创建的实体中找到 toString()方法. 该方法会在 my 对象中被调用, 不过 this 指向的是 my, 所以 return 的 my.name 是 Triangle.
- // 通过 instanceof 操作符, 可以验证 my 对象同时是上述三个构造器的实例;
- my instanceof Shape;
结果: true
my instanceof TwoDShape;
结果: true
my instanceof Triangle;
结果: true
6.1.2 将共享属性迁移到原型中去
- // 用 new Shape()创建的每个实体都会拥有一个全新的 name 属性, 并在内存中拥有自己独立的存储空间
- function Shape() {
- this.name = 'Shape';
- }
- // 可以将 name 属性添加到原型上去, 这样一来所有实体就可以共享这个属性了, 这种做法适合对象的共有方法的情况, 针对对象实体中的不可变属性而言的.
- function Shape() {
- Shape.prototype.name = 'Shape';
- }
示例:
- function Shape() {
- //argument prototype
- Shape.prototype.name = 'Shape';
- Shape.prototype.toString = function() {
- return this.name;
- }
- }
- function TwoDShape() {}
- // 注意继承关系
- TwoDShape.prototype = new Shape();
- TwoDShape.prototype.constructor = TwoDShape;
- // 参数原型
- TwoDShape.prototype.name = '2D shape';
- // 注意, 首先完成相关的继承关系构建, 然后再对原型对象进行扩展
- function Triangle(side, height) {
- this.side = side;
- this.height = height;
- }
- Triangle.prototype = new TwoDShape();
- Triangle.prototype.constructor = Triangle;
- Triangle.prototype.name = 'Triangle'; // 设置为共享属性
- Triangle.prototype.getArea = function(){ // 设置为共享属性
- return this.side * this.height / 2;
- };
- //tips: 这里 Triangle 构造器所创建的各个对象所表示的三角形在尺寸上各不相同, 因此, 该对象的 side 和 height 这两个属性必须保持自身所有, 不必设置为共享属性.
与上一小节的示例相比, 这里调用 my.toString()的区别仅仅在于幕后的少量操作. 这里可以直接在 Shape.prototype 中查找, 而不用再到 new Shape()所创建的实体对象中查找了.
6.2 只继承于原型
- // 设置构造器原型时, 直接用另一个构造器的原型来覆盖
- TwoDShape.prototype = Shape.prototype;
- Triangle.prototype = TwoDShape.prototype;
这里 my.toString()方法查找的时候, 就会直接搜索该对象的原型属性, 此时, 该原型已经指向 TwoDShape 的原型, 而 TwoDShape 的原型指向的又是 Shape.prototype, 所以这里的步骤就被精简成了两步.
这样的方法提升了效率, 但是副作用就是一旦子对象对其原型进行修改, 父对象也会随即被改变.
举例: 原来 new Shape()的 name 值为'Shape', 但是如果用了上述方法之后, new Shape()的 name 值就会变成'Triangle', 因为 new Triangle()已经把 name 值改成了'Triangle'.
临时构造器 new F()
- // 利用临时构造器 new F(), 来解决修改子对象原型时, 父对象也会随之改变的问题
- function Shape() {
- Shape.prototype.name = 'Shape';
- Shape.prototype.toString = function () {
- return this.name;
- }
- }
- function TwoDShape(){};
- var F = function () {};
- F.prototype = Shape.prototype;
- TwoDShape.prototype = new F();
- TwoDShape.prototype.constructor = TwoDShape;
- TwoDShape.prototype.name = '2D shape' // 设置共享属性
- function Triangle(side, height) {
- this.side = side;
- this.height = height;
- }
- var F = function () {};
- F.prototype = TwoDShape.prototype;
- Triangle.prototype = new F();
- Triangle.prototype.constructor = Triangle; // 构建继承关系
- Triangle.prototype.name = 'Triangle';
- Triangle.prototype.getArea = function () {
- return this.side * this.height / 2;
- }
- // 创建一个对象, 来测试
- var my = new Triangle(5, 10);
- my.getArea();
结果: 25
- // 这样就可以保持住原型链
- my._proto_ == Triangle.prototype;
- my._proto_._proto_ == TwoDShape.prototype;
- my._proto_._proto_._proto_ == Shape.prototype;
- my._proto_._proto_._proto_.constructor == Shape;
- var s = new Shape();
- s.name == 'Shape';
- // 结果说明, 父对象的属性也没有被子对象所覆盖
6.3 uber - 子对象访问父对象的方式
- function Shape() {};
- Shape.prototype.name = 'Shape';
- Shape.prototype.toString = function () {
- var const = this.constructor;
- return const.uber ? this.const.uber.toString() + ',' + this.name : this.name; // 这里对 toString()方法进行更新, 即检查对象中是否存在 this.constructor.uber 属性, 如果存在, 就先调用该属性的 toString 方法.
- };
- function TwoDShape(){};
- var F = function () {};
- F.prototype = Shape.prototype;
- TwoDShape.prototype = new F();
- TwoDShape.prototype.constructor = TwoDShape;
- TwoDShape.uber = Shape.prototype; // uber 属性设置成指向其父级原型的引用
- TwoDShape.prototype.name = '2D shape' // 设置共享属性
- function Triangle(side, height) {
- this.side = side;
- this.height = height;
- }
- var F = function () {};
- F.prototype = TwoDShape.prototype;
- Triangle.prototype = new F();
- Triangle.prototype.constructor = Triangle; // 构建继承关系
- Triangle.uber = TwoDShape.prototype; // uber 属性设置成指向其父级原型的引用
- Triangle.prototype.name = 'Triangle';
- Triangle.prototype.getArea = function () {
- return this.side * this.height / 2;
- }
- // 创建一个对象, 来测试
- var my = new Triangle(5, 10);
不知道这里为什么报错了, const 这个可能是跟 ES6 有冲突, 但是改了之后还是报错
上述示例中:
派生的层次是: Shape -> TwoDShape -> Triangle;
uber 属性: 指向父类原型;
toString()方法中, 检查构造函数的父类的原型是否存在, 如果存在, 则调用其 toString()方法, 由此实现了在子类中调用父类方法.
6.4 将继承部分封装成函数
来源: http://www.qdfuns.com/note/46360/9699d383af8c777d9feca5cb0097fc0a.html