-
-
Save nzakas/4289220 to your computer and use it in GitHub Desktop.
/* | |
* This is just an experiment. Don't read too much into the fact that these are global variables. | |
* The basic idea is to combine the two steps of defining a constructor and modifying a prototype | |
* into just one function call that looks more like traditional classes and other OO languages. | |
*/ | |
// Utility function | |
function mixin(receiver, supplier) { | |
if (Object.getOwnPropertyDescriptor) { | |
Object.keys(supplier).forEach(function(property) { | |
var descriptor = Object.getOwnPropertyDescriptor(supplier, property); | |
Object.defineProperty(receiver, property, descriptor); | |
}); | |
} else { | |
for (var property in supplier) { | |
if (supplier.hasOwnProperty(property)) { | |
receiver[property] = supplier[property] | |
} | |
} | |
} | |
return receiver; | |
} | |
/** | |
* Creates a new constructor with appropriate prototype members. If there's only one | |
* argument, it's considered the declaration. When you want to inherit from another | |
* object or constructor, then that is the first argument and the declaration is second. | |
* I considered always having the declaration as the first argument, but found it was | |
* easy to forget to do include the prototype as the second argument. Also, this | |
* allows you to look at the top of the type declaration to see if there is any | |
* inheritance, whereas having it as a second argument could lead to it being overlooked. | |
* | |
* @param {Function|Object} prototype (optional) The prototype for the new type. If | |
* this is a function, then the function's prototype is used. If this | |
* is an object, then that object is used. If omitted, Object.prototype | |
* is used as is the case for all generic objects. | |
* @param {Object} declaration The object literal containing at least a constructor | |
* function. All other methods are added to the resulting constructor's | |
* prototype. If there's only one argument to the function, then it is considered | |
* to be the declaration. | |
*/ | |
function type(prototype, declaration) { | |
// if there's only one argument, then the first argument is the declaration | |
if (!declaration) { | |
declaration = prototype; | |
declaration.constructor.prototype = declaration; | |
} else { | |
// make sure the prototype is an object | |
prototype = (typeof prototype == "function") ? prototype.prototype : prototype; | |
// create a new prototype for the constructor function | |
declaration.constructor.prototype = Object.create(prototype, { | |
constructor: { | |
configurable: true, | |
enumerable: true, | |
value: declaration.constructor, | |
writable: true | |
} | |
}); | |
// add everything from the declaration onto the new prototype | |
mixin(declaration.constructor.prototype, declaration); | |
} | |
// return the now-complete constructor function | |
return declaration.constructor; | |
} | |
//--------------------------------------------------------------------------- | |
// Usage | |
//--------------------------------------------------------------------------- | |
var Rectangle = type({ | |
constructor: function(length, width) { | |
this.length = length; | |
this.width = width; | |
}, | |
getArea: function() { | |
return this.length * this.width; | |
} | |
}); | |
// inherit from rectangle | |
var Square = type(Rectangle, { | |
constructor: function(size) { | |
Rectangle.call(this, size, size); | |
} | |
}); | |
var rect = new Rectangle(3, 10); | |
console.log(rect instanceof Rectangle); // true | |
console.log(rect.constructor === Rectangle); // true | |
console.log(rect.getArea()); // 30 | |
var square = new Square(10); | |
console.log(square instanceof Square); // true | |
console.log(square instanceof Rectangle); // true | |
console.log(square.constructor === Square); // true | |
console.log(square.constructor === Rectangle); // false | |
console.log(square.getArea()); // 100 |
There are two things here: the syntax for creating a custom type and then how the prototype is assigned.
My main goal with this was to create a more succinct syntax for creating a custom type. I've always hated needing to create a constructor and then needing to manually modify the prototype. I wanted to do that in just one step.
Object.create()
effectively does the same thing as your first example using tmp
, which is the same as Crockford's object()
function. Your second example is, once again, the same as using Object.create()
just with __proto__
to do the assignment instead. So your two examples and my approach all essentially work the same way - these are all just different ways of assigning a prototype without needing to call the supertype constructor again.
Fair 'nuf.
What's the upshot of this opposed to the classic approach using a temporary function with the target constructor prototype. Could even use
__proto__
, although that's not backwards compat, which I assume you still want.Classic approach:
And with dunderproto: