可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm rewriting a JavaScript project, and I want to be able to use object oriented methodologies to organize the mess that the current code is. The main concern is that this JavaScript is supposed to run as a widget inside 3rd party websites and I can't have it conflicting with other JavaScript libraries that other websites may use.
So I'm looking for a way to write "class-like" inheritance in JavaScript that has the following requirements:
- No external libraries or things that would conflict with an external library (that precludes copy&paste from an external library).
- Minimalistic - I don't want the support code to be larger then a few lines of code and I don't want the developers to need a lot of boiler-plate every time they define a new class or methods.
- Should allow for dynamically extending parent objects so that child objects see the changes (prototype).
- Should allow for constructor chaining.
- Should allow for
super
type calls.
- Should still feel JavaScript-ish.
Initially I tried to work with simple prototype chaining:
function Shape(x,y) {
this.x = x;
this.y = y;
this.draw = function() {
throw new Error("Arbitrary shapes cannot be drawn");
}
}
function Square(x,y,side) {
this.x = x;
this.y = y;
this.side = side;
this.draw = function() {
gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ...
}
}
Square.prototype = new Shape();
And that solves requirements 1, 2 and 6 but id does not allow super calls (new functions override parent functions), constructor chaining and dynamically extending a parent does not provide the new methods to a child class.
Any suggestions will be welcome.
回答1:
I'd suggest the following pattern which makes use of a clone
function to inherit from the protoypes and not instances:
function Shape(x, y) {
this.x = x;
this.y = y;
}
Shape.prototype.draw = function() {
throw new Error('Arbitrary shapes cannot be drawn');
};
function Square(x,y,side) {
Shape.call(this, x, y); // call super constructor
this.side = side;
}
// inherit from `Shape.prototype` and *not* an actual instance:
Square.prototype = clone(Shape.prototype);
// override `draw()` method
Square.prototype.draw = function() {
gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
};
It's important that methods reside in the prototype (which is as it should be anyway for performance reasons) so you can call the methods of a super class via
SuperClass.prototype.aMethod.call(this, arg1, arg2);
With some syntactic sugar, you can make JS look like a classical class-based language:
var Shape = Class.extend({
constructor : function(x, y) {
this.x = x;
this.y = y;
},
draw : function() {
throw new Error('Arbitrary shapes cannot be drawn');
}
});
var Square = Shape.extend({
constructor : function(x, y, side) {
Shape.call(this, x, y);
this.side = side
},
draw : function() {
gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
}
});
回答2:
Douglas Crockford has good articles on both classical and prototypal inheritance in Javascript, which should make good starting points.
回答3:
OK, the trick with reproducing a class/instance-style system in JavaScript is that you can only use prototype inheritance on the instances. So you need to be able to make a ‘non-instance’ instance that is only used for inheritance, and have an initialiser method separate from the constructor function itself.
This is the minimal system I use (before adding frills), passing a special one-off value into the constructor to have it construct an object without initialising it:
Function.prototype.subclass= function() {
var c= new Function(
'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
);
if (this!==Object)
c.prototype= new this(Function.prototype.subclass._FLAG);
return c;
};
Function.prototype.subclass._FLAG= {};
The use of new Function()
is a way to avoid forming an unnecessary closure over subclass(). You can replace it with a prettier function() {...}
expression if you prefer.
Usage is comparatively clean, and generally like Python-style objects only with slightly clumsier syntax:
var Shape= Object.subclass();
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
Shape.prototype.draw= function() {
throw new Error("Arbitrary shapes cannot be drawn");
};
var Square= Shape.subclass();
Square.prototype._init= function(x, y, side) {
Shape.prototype._init.call(this, x, y);
this.side= side;
};
Square.prototype.draw= function() {
gotoXY(this.x, this.y);
lineTo(this.x+this.side, this.y); // ...
};
Monkey-patching a builtin (Function) is a bit questionable, but makes it pleasant to read, and no-one's likely to want to for...in
over a Function.
回答4:
The most common pattern I found when researching this question is described on the Mozilla Developer Network. I've updated their example to include a call to a superclass method and to show the log in an alert message:
// Shape - superclass
function Shape() {
this.x = 0;
this.y = 0;
}
// superclass method
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
log += 'Shape moved.\n';
};
// Rectangle - subclass
function Rectangle() {
Shape.call(this); // call super constructor.
}
// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
// Override method
Rectangle.prototype.move = function(x, y) {
Shape.prototype.move.call(this, x, y); // call superclass method
log += 'Rectangle moved.\n';
}
var log = "";
var rect = new Rectangle();
log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
alert(log);
- In the five years since you asked this question, it seems like browser support for inheritance has improved, so I don't think you need an external library.
- This the most minimal technique I've seen, I don't know if you consider that too much boilerplate.
- It uses the prototype, as requested, so adding new methods to the parent should provide them to the child objects as well.
- You can see constructor chaining in the example.
- Super type calls are also in the example.
- I'm not sure if it feels JavaScript-ish, you'll have to decide for yourself.
回答5:
You can use functional pattern proposed by Crockford in his book "JavaScript the good parts". Idea is use closore to make private fields, and use previleged function to access to these fields. Here is one of solutions which meet your 6 requirements:
var people = function (info) {
var that = {};
// private
var name = info.name;
var age = info.age;
// getter and setter
that.getName = function () {
return name;
};
that.setName = function (aName) {
name = aName;
};
that.getAge = function () {
return age;
};
that.setAge = function (anAge) {
age = anAge;
};
return that;
};
var student = function (info) {
// super
var that = people(info);
// private
var major = info.major;
that.getMajor = function () {
return major;
};
that.setMajor = function (aMajor) {
major = aMajor;
};
return that;
};
var itStudent = function (info) {
// super
var that = student(info);
var language = info.language;
that.getLanguage = function () {
return language;
};
that.setLanguage = function (aLanguage) {
language = aLanguage;
};
return that;
};
var p = person({name : "Alex", age : 24});
console.debug(p.age); // undefined
console.debug(p.getAge()); // 24
var s = student({name : "Alex", age : 24, major : "IT"});
console.debug(s.getName()); // Alex
console.debug(s.getMajor()); // IT
var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"});
console.debug(i.language); // Undefined
console.debug(i.getName()); // Alex
console.debug(i.getMajor()); // IT
console.debug(i.getLanguage()); // js
回答6:
Also Crockford-inspired, but I had good experiences with what he calls "functional inheritance" using "constructor functions". YMMV.
UPDATE: Sorry, I forgot: you still need to augment Object with a superior
method to get nice access to a super method. Not a good fit for you, probably.
var makeShape = function (x, y) {
that = {};
that.x = x;
that.y = y;
that.draw = function() {
throw new Error("Arbitrary shapes cannot be drawn");
}
return that;
};
var makeSquare = function (x, y, side) {
that = makeShape(x, y);
that.side = side;
that.draw = function() {
gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ...
}
return that;
};