JavaScript的原型以及继承机制 (英文)

在公司内曾经用英文写过一篇关于JavaScript的原型以及继承机制的启蒙文章。
有空的话余用中文重写一次。

I will talk something about prototype and inheritance of JavaScript in this article.

If a class is alone, a.k.a, it has no superclass nor subclass, and such instances will be very few in memory, it is OK to define private members in javascript-closure.

Let’s call it “closure-style”.

foo = function() {
  /** private member */
  var _colors = []; // array is poor performance, just for example

  this.addColor = function(color) {
    if (!this.hasColor(color)) {
      _colors.push(color);
    }
  };

  this.hasColor = function(color) {
    return goog.array.contains(_colors, color);
  };

  this.removeColor = function(color) {
    var index = goog.array.indexOf(_colors, color);
    if (index >= 0) {
      goog.array.removeAt(_colors, index);
    }
  };
};

var f1 = new foo();
var f2 = new foo();
f1.addColor === f2.addColor; // false

var arr = [];
var timestamp = new Date;
for (var i = 0; i < 100000; ++i) {
  arr[i] = new foo();
}
console.log(new Date - timestamp);

Functions are also instances, but they are not shared when we use “closure-style”.
That is why prototype is recommended. We can share the functions between instances constructed with same constructor.

We use annotation to mark the private members, and the advanced compiler such as Google Closure Compiler, is able to check if the rule has been broken or not.

foo = function() {
  /**
   * @private
   * @type {Array}
   */
  this.colors_ = []; // array is poor performance, just for example
};

foo.prototype.addColor = function(color) {
  if (!this.hasColor(color)) {
    this.colors_.push(color);
  }
};

foo.prototype.hasColor = function(color) {
  return goog.array.contains(this.colors_, color);
};

foo.prototype.removeColor = function(color) {
  var index = goog.array.indexOf(this.colors_, color);
  if (index >= 0) {
    goog.array.removeAt(this.colors_, index);
  }
};

var f1 = new foo();
var f2 = new foo();
f1.addColor === f2.addColor; // true

var arr = [];
var timestamp = new Date;
for (var i = 0; i < 100000; ++i) {
  arr[i] = new foo();
}
console.log(new Date - timestamp);

Because public methods have to access private members, private member can not be written in “closure-style”.
Let’s call it “prototype-style”.

Functions on prototype chain will be shared between instances. There is only 1 copy.
When there are thousands of such instances, we can save memory and time by using “prototype-style”.

I most want to talk about inheritance, it is the reason why I suggest to use “prototype-style”.

Consider a new class inherited from ‘foo’:

bar = function() {
  // some codes here
};

bar.prototype = new foo();

var b1 = new bar();
var b2 = new bar();

b1.addColor('red');
b2.hasColor('red'); // true

No matter which style (“prototype-style” or “closure-style”) we use, the private member become shared in prototype chain.
All the instances of “bar” will share a same Object as prototype parent, which is an instance of “foo”.
So, not only methods in “foo” are shared, but also private members in “foo” too.
(Another problem is parameters can not be passed to superclass’s constructor.)

The “prototype-style” has a simple solution to fix the problem, when “closure-style” can not be fixed by this solution.

bar = function() {
  this.colors_ = [];
};

bar.prototype = new foo();

var b1 = new bar();
var b2 = new bar();

b1.addColor('red');
b2.hasColor('red'); // false

But when the prototype chain is growing, it is so stupid and ugly to define private members of superclasses in constructor again and again.

Google Closure library provides an inheritance solution (Parasitic Combination Inheritance) for us, basing on “prototype-style”.
We can:
1. Reuse constructor of superclass.
2. Share all prototype-chain functions between instances of a class.
3. Member variables of superclass are separated between instances of subclass.
4. Override and reuse prototype-chain functions easily.
5. still use instanceof and isPrototypeOf normally.
6. Earn the high performance brought from ADVANCED OPTIMIZATIONS of Closure Compiler.

bar = function() {
  // call constructor of superclass
  goog.base(this);
};
goog.inherits(bar, foo);

var b1 = new bar();
var b2 = new bar();

b1.addColor('red');
b2.hasColor('red'); // false

b1.addColor === b2.addColor // true

Override will become very simple:

bar = function() {
  // call constructor of superclass
  goog.base(this);

  /**
   * @private
   * @type {Object}
   */
  this.colormap_ = {};
};
goog.inherits(bar, foo);

/**
 * @override
 */
bar.prototype.addColor = function(color) {
  if (this.colormap_[color]) {
    return;
  }
  // reuse function
  goog.base(this, 'addColor', color);
  this.colormap_[color] = true;
};

/**
 * @override
 */
bar.prototype.hasColor = function(color) {
  return !!this.colormap_[color];
};

/**
 * @override
 */
bar.prototype.removeColor = function(color) {
  if (!this.hasColor(color)) {
    return;
  }
  // reuse function
  goog.base(this, 'removeColor', color);
  delete this.colormap_[color];
};

var b1 = new bar();
var b2 = new bar();
b1.addColor('red');
b2.hasColor('red'); // false
b1.addColor === b2.addColor // true

var f1 = new foo();
b1.addColor === f1.addColor // false

b1 instanceof bar; // true
b1 instanceof foo; // true
f1 instanceof bar; // false
f1 instanceof foo; // true

Object.prototype.isPrototypeOf(b1); // true
foo.prototype.isPrototypeOf(b1); // true
bar.prototype.isPrototypeOf(b1); // true

foo.prototype.isPrototypeOf(f1); // true
bar.prototype.isPrototypeOf(f1); // false

The solution from Google Closure Library is suitable for complicated Class architecture, which might be used in a big application holding thousands of Objects in memory.

I don’t know there is a similar mechanism in jQuery or not.
But if you are just using Google Closure Library, please consider about “prototype-style” and google’s solution first.

If you are not using Google Closure Library, but have to design a Complicated Class architecture, and have no idea about Inheritance of JavaScript, I think “prototype-style” will be much suitable, please google “Parasitic Combination Inheritance” and implement one. I think it is not so difficult.

See also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain

本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: