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
评论