js实现继承的几种方式

6,501 阅读5分钟

经常浏览各种文章,学习各种技术,看了,不用,忘了.

好记性,不如烂笔头!

继承概念:

继承机制实例

说明继承机制最简单的方式是,利用一个经典的例子 - 几何形状。实际上,几何形状只有两种,即椭圆形和多边形。圆是椭圆的一种,它只有一个焦点。三角形、矩形和五边形都是多边形的一种,具有不同数量的边。这就构成了一种完美的继承关系。

在这个例子中,形状 是椭圆形和多边形的基类(base class)(所有类都由它继承而来)。圆形继承了椭圆形,因此圆形是椭圆形的子类(subclass),椭圆形是圆形的超类(superclass)。同样,三角形、矩形和五边形都是多边形的子类,多边形是它们的超类。最后,正方形(Square)继承了矩形。

下面的图示是解释 Shape 和它的子类之间关系的 图示:

继承机制 UML 图示实例


在讲解继承之前先了解一下JavaScript中创建一个对象的过程

function Person(name){
    //调用new的过程相当于 var this=new Object();
    //给this对象赋值
    //最后return this;
    this.name=name;
};
Person.prototype.getName=function(){
return this.name;
};
var a=new Person("seven");
console.log(a.name); //输出:seven
console.log(a.getName); //输出:seven

console.log(Object.getPrototypeOf(a)===Person.prototype); //输出:true

javaScript的函数可以被当做普通函数调用,也可以作为构造器调用.


javaScript中的原型继承

原型编程的规则:

  • 1.所有数据都是对象
  • 2.要得到一个对象不是实例化类,而是找到一个对象作为原型并克隆ta
  • 3.对象会记住它的原型(__proto__)
  • 4.如果对象无法响应某个请求,ta会把这个请求委托给自动的构造函数的原型


继承的方式

ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。

对象冒充

因为构造函数只是一个函数,所以可使 ClassA 构造函数成为 ClassB 的方法,然后调用它。ClassB 就会收到 ClassA 的构造函数中定义的属性和方法。例如,用下面的方式定义 ClassA 和 ClassB:

function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
}

在这段代码中,为 ClassA 赋予了方法 newMethod(请记住,函数名只是指向它的指针)。然后调用该方法,传递给它的是 ClassB 构造函数的参数 sColor。最后一行代码删除了对 ClassA 的引用,这样以后就不能再调用它。

所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法:

function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

为证明前面的代码有效,可以运行下面的例子:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//输出 "blue"
objB.sayColor();	//输出 "red"
objB.sayName();		//输出 "John"

可以使用Call方法优化一下

function ClassB(sColor, sName) {
    //this.newMethod = ClassA;
    //this.newMethod(color);
    //delete this.newMethod;

    ClassA.call(this, sColor);
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

这种方式只是ClassB调用了ClassA,给ClassB的this赋值

原型链(prototype chaining)

function ClassA() {
}

ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB() {
}

ClassB.prototype = new ClassA();

原型方式的神奇之处在于最后一行代码。这里,把 ClassB 的 prototype 属性设置成 ClassA 的实例。因为想要 ClassA 的所有属性和方法,但又不想逐个将它们 ClassB 的 prototype 属性。还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗?

调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为 ClassB 类添加 name 属性和 sayName() 方法的代码如下:

function ClassB() {
}

ClassB.prototype = new ClassA();

ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};

可通过运行下面的例子测试这段代码:

var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName()

混合方式

创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子,代码如下:

function ClassA(sColor) {
    this.color = sColor;
}

ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB(sColor, sName) {
    ClassA.call(this, sColor);//对象冒充
    this.name = sName;
}

ClassB.prototype = new ClassA();//原型

ClassB.prototype.sayName = function () {
    alert(this.name);
};

在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性。用原型链继承 ClassA 类的方法。

ES6 实现了Class,可以通过关键字extends实现继承

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);    this.color = color; // 正确
  }
}

转换成es5

"use strict";

function _possibleConstructorReturn(self, call) { 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

function _inherits(subClass, superClass) { 
  subClass.prototype = Object.create(
      superClass && superClass.prototype, 
      { 
      constructor: { 
        value: subClass, 
        enumerable: false, 
        writable: true, 
        configurable: true 
      } 
      }
  );
    if (superClass) {
      Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    }
  }

var Point = function Point(x, y) {
  this.x = x;
  this.y = y;
};

var ColorPoint = function (_Point) {
  _inherits(ColorPoint, _Point);

  function ColorPoint(x, y, color) {
    var _this = _possibleConstructorReturn(this, (ColorPoint.__proto__ || Object.getPrototypeOf(ColorPoint)).call(this, x, y));

    _this.color = color; // 正确
    return _this;
  }

  return ColorPoint;l
}(Point);

这里重点讲解一下ES5中的继承

1. _inherits,首先使用Object.create将父类Point原型拷贝到子类ColorPoint上

2.将父类构造函数挂载到子类的__proto__ 上

,保持对父类的应用

3.当调用子类
ColorPoint,初始化时调用ColorPoint.__proto__.call(this, x, y),将父类中的属性挂载到子类的this中

非构造函数的继承

比如,现在有一个对象,叫做"中国人"。

  

var Chinese = {
    nation:'中国'
  };

还有一个对象,叫做"医生"。


 var Doctor ={
    career:'医生'
  }

请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?

可以利用空构造函数:

function extends(o) {
    function F() {}
    F.prototype = o;
    return new F();
  }
var Doctor = object(Chinese);
Doctor.career = '医生';

也可以使用Object.create()

var Doctor = Object.create(Chinese);
Doctor.career = '医生';

到此结束!