在 JavaScript 中实现 Java 式的类继承


JavaScript 和 Java 等强类型面向对象语言的不同之处在于,JavaScript 中的函数都是以值的形式出现的,方法和字段之间没有太大的区别。如果属性值是函数,那么这个属性就定义了一个方法,否则它就是一个普通的属性或「字段」。JavaScript 中的类涉及到三种不同的对象:

  • 构造函数对象:构造函数为 JavaScript 的类定义了名字,任何添加到构造函数的属性都是类字段和类方法(属性值为函数就是类方法)
  • 原型对象:原型对象的属性被类的所有实例继承,如果原型对象的属性值是函数,这个函数就作为类的示例的方法调用
  • 实例对象:类的每个实例都是独立的对象,直接给这个实例定义的属性是不会为所有实例对象所共享的

在 JavaScript 中定义类的步骤可以缩减为一个分三步的算法,第一步,先定义一个构造函数,并设置初始化新对象的实例属性;第二步,给构造函数的 prototype 对象定义实例的方法;第三步,给构造函数定义类字段和类属性。我们将这三步操作封装到一个 defineClass() 函数中:

/**
 * 一个用以定义简单类的函数
 * @param constructor 用于设置实例属性的函数
 * @param methods 实例方法,会被复制到原型对象中
 * @param statics 类属性,会被复制到构造函数中
 * @returns constructor
 */
function defineClass(constructor, methods, statics) {
  if (methods)
    extend(constructor.prototype, methods);
  if (statics)
    extend(constructor, statics);
  return constructor;
}

/**
 * 把 p 中的可枚举属性复制到 o 中,并返回 o
 * @param o
 * @param p
 * @returns o
 */
function extend(o, p) {
  for (prop in p) {
    o[prop] = p[prop];
  }
  return o;
}

// 这是 Range 类的另一个实现版本
var SimpleRange = defineClass(function (f, t) {
  this.f = f;
  this.t = t;
}, {
  includes: function (x) {
    return this.f <= x && x <= this.t;
  },
  toString: function () {
    return this.f + '...' + this.t;
  }
}, {
  upto: function (t) {
    return new SimpleRange(0, t);
  }
});

下面我们将在 JavaScript 中模拟类似 Java 的类实现,定义一个表示复数的类 Complex,实现构造函数、实例字段、实例方法、类字段和类方法:

/**
 * Complex.js
 * 这个文件定义了 Complex 类,用来描述复数
 */

/**
 * 这个构造函数为它所创建的每个实例定义了实例字段r和i
 * @param real 复数的实部
 * @param imaginary 复数的虚部
 * @constructor
 */
function Complex(real, imaginary) {
  if (isNaN(real) || isNaN(imaginary)) {  // 确保两个实参都是数字
    throw new TypeError();
  }
  this.r = real;
  this.i = imaginary;
}

// 下面定义类的实例方法

/**
 * 复数相加
 * @param that
 * @returns {Complex}
 */
Complex.prototype.add = function (that) {
  return new Complex(this.r + that.r, this.i + that.i);
};

/**
 * 复数相乘
 * @param that
 * @returns {Complex}
 */
Complex.prototype.mul = function (that) {
  return new Complex(this.r * that.r - this.i * that.i, this.r * that.i + this.i * that.r);
};

/**
 * 计算复数的模
 * @returns {number}
 */
Complex.prototype.mag = function () {
  return Math.sqrt(this.r * this.r + this.i * this.i);
};

/**
 * 复数的求负运算
 * @returns {Complex}
 */
Complex.prototype.neg = function () {
  return new Complex(-this.r, -this.i);
};

/**
 * 将复数对象转换为字符串
 * @returns {string}
 */
Complex.prototype.toString = function () {
  return "{" + this.r + "," + this.i + "}";
};

/**
 * 判断两个复数是否相等
 * @param that
 * @returns {boolean}
 */
Complex.prototype.equals = function (that) {
  return that !== null && that.constructor === Complex &&
    this.r === that.r && this.i === that.i;
};

// 下面定义类字段(常量)和类方法(静态方法)

Complex.ZERO = new Complex(0, 0);
Complex.ONE = new Complex(1, 0);
Complex.I = new Complex(0, 1);

// 这个类方法将由实例对象的 toString 方法返回的字符串解析为一个 Complex 对象
Complex.parse = function (s) {
  try {
    var m = Complex._format.exec(s);
    return new Complex(parseFloat(m[1]), parseFloat(m[2]));
  } catch (x) {
    throw new TypeError("Can't parse '" + s + "' as a complex number.");
  }
};

// 定义类的私有字段,下划线前缀表明它是在类的内部使用
Complex._format = /^\{([^,]+),([^}]+)\}$/;

下面我们对复数类进行调用:


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 类和构造函数

>> 下一篇: 类的扩展