I've recently read this article, and I agree that the new
keyword is not good practice.
Thus, I've made an improvement on John Resig's Simple JavaScript Inheritance in order to use the Class.create
method instead of new
:
// The dummy class constructor
function Class() {
// I remove the initialization procedure in constructor function,
// Initialization will done by Class.create which I defined below
}
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
// What I improved
Class.create = function () {
var instance = new this();
if (instance.init) {
instance.init();
}
return instance;
}
The initialization case in his article could be rewritten like this:
var Person = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
}
});
var p = Person.create();
Have I done this well or not? Please help me check it.
-
1\$\begingroup\$ you are still using the 'new' keyword... \$\endgroup\$Pinoniq– Pinoniq2013年08月21日 11:57:50 +00:00Commented Aug 21, 2013 at 11:57
2 Answers 2
If you really want to create your own inheritance pattern then John Resig's Simple Inheritance pattern is not a good candidate to build upon. The reason is because John Resig's code is very slow. The reason it's so slow is because of the way _super
is handled:
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
I'll try to explain what's happening in the above code:
- We loop through each property which we wish to copy onto the newly extended object.
- If the property is a function and it overrides another function of the same name and it needs to call the overridden function then we replace it with a function which changes the value of
this._super
within the function to the overridden function for that particular function call. - Otherwise we simply copy the property as it is.
This little indirection allows you to call overridden methods using this._super
. However it also makes the code very slow. Hence I suggest you don't use John Resig's Simple JavaScript Inheritance.
Since this is a code review site I'm also obliged to point out flaws in your code. The main problem with your code is in the Class.create
function:
Class.create = function () {
var instance = new this();
if (instance.init) {
instance.init();
}
return instance;
};
Compare that with John Resig's original dummy class constructor:
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
The problem is that any argument applied to Class.create
is lost. You need to apply
the arguments
passed to Class.create
to instance.init
as follows:
Class.create = function () {
var instance = new this;
if (instance.init) instance.init.apply(instance, arguments);
return instance;
};
Beside that there's not much more scope for improvement if you plan to stick to John Resig's Simple Inheritance Pattern.
If you really want to create your own inheritance pattern in JavaScript then it pays to invest some time learning about how inheritance works in JavaScript. For example the following answer explains prototype-class isomorphism in JavaScript: https://stackoverflow.com/a/17893663/783743
Prototype-class isomorphism simply means that prototypes can be used to model classes. Armed with this knowledge we can write a function which takes an object (a prototype) and returns a class (a constructor function):
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Using the above function we may now create and instantiate classes as follows:
var Person = CLASS({
constructor: function (isDancing) {
this.dancing = isDancing;
},
dance: function () {
return this.dancing;
}
});
var p = new Person(true);
Although this pattern does not have inheritance yet it is a good base to build upon. With a little bit of modification we can make it behave the way we want it to. There are a few important points to note:
- We want to be able to use the
extend
andcreate
functions on any function. Hence it's better to add them toFunction.prototype
instead of creating a separateClass
constructor. - For a function
F
it need not be true thatF.prototype.constructor === F
. Hence we may useF
for extending andF.prototype.constructor
for creating. - Instead of passing a prototype to
extend
it makes much more sense to pass a blueprint of a prototype instead. This makes looping over the prototype unnecessary.
Taking advantage of the above mentioned points we can implement extend
and create
as follows:
Function.prototype.extend = function (body) {
var constructor = function () {};
var prototype = constructor.prototype = new this;
body.call(prototype, this.prototype);
return constructor;
};
Function.prototype.create = function () {
var instance = new this;
instance.constructor.apply(instance, arguments);
return instance;
};
That's all. Using two simple functions we may now create classes as follows:
var Person = Object.extend(function () {
this.constructor = function (isDancing) {
this.dancing = isDancing;
};
this.dance = function () {
return this.dancing;
};
});
var Ninja = Person.extend(function (base) {
this.constructor = function () {
base.constructor.call(this, false);
};
this.swingSword = function () {
return true;
};
});
To create an instance of the class we use create
as follows:
var p = Person.create(true);
p.dance(); // => true
var n = Ninja.create();
n.dance(); // => false
n.swingSword(); // => true
// Should all be true
p instanceof Person && p instanceof Object &&
n instanceof Ninja && n instanceof Person && n instanceof Object
This code is similar to my very own augment
function. However augment
makes use of new
instead of create
. Because of the way JavaScript works, using new
is the fastest way to create an instance which is why augment
doesn't have create
. Nevertheless if you want it, a functional version of new
can be easily implemented as follows:
function Factory(constructor, args) {
return constructor.apply(this, args);
}
Function.prototype.new = function () {
Factory.prototype = this.prototype;
return new Factory(constructor, args);
};
For more information read the following answer on StackOverflow.
arguments.callee
is deprecated and suggest you actually avoid it.What you just did was hide the
new
insidecreate()
. That's nothing new. In fact, libraries like jQuery do this. Calling$()
is actually making a new jQuery object.jQuery === $ === function (a,b){return new e.fn.init(a,b,h)}
new
isn't bad practice. It depends on the developer. JavaScript gives you that flexibility to do anything with it. I once considered it as a problem, but soon learned that there's a place for it. It's easy to use (and abuse) once you know how prototypal OOP works in JS.Additional complexity might be a hit in performance. I'd stick to the native way of doing things unless circumstances call for extreme measures. What's wrong with JS's way of doing inheritance?