Trying to implement an easily-"subclassable" Array subclass - javascript

I'm trying to subclass Array, following the ideas and recommendations given in this article.
One important goal of this subclass of Array, which here I'm calling ArrayBase, is that it be itself more straightforward to subclass than Array itself. I'm finding that achieving this goal is surprisingly difficult. (But it may look this way to me because I'm a JavaScript n00b!)
Below is an implementation of ArrayBase that is based on the ideas presented towards the end of the article cited earlier, with some enhancements of my own. I've also included an implementation of ArrayBase.slice, since it illustrates one problem with the scheme1.
function ArrayBase () {
var arr = Array.prototype.constructor.apply(null, arguments);
var ctor = arr.constructor = arguments.callee;
arr.__proto__ = ctor.prototype;
return arr;
}
ArrayBase.prototype = new Array;
ArrayBase.prototype.slice = function () {
var ctor = this.constructor;
return ctor.apply(null,
Array.prototype.slice.apply(this, arguments));
}
var a0 = new ArrayBase(0, 1, 2, 3);
var a1 = a0.slice(2); // [2, 3]
console.log(a1 instanceof ArrayBase); // true
console.log(a1 instanceof Array); // true
So far, so good. The problem happens when I now try to subclass ArrayBase. I find that the only way to do this requires basically replicating the entire ArrayBase constructor (the only difference, very slight, happens in the first line). As inheritance goes, this is pitiful...
function MyArray () {
var arr = ArrayBase.apply(this, arguments);
var ctor = arr.constructor = arguments.callee;
arr.__proto__ = ctor.prototype;
return arr;
}
MyArray.prototype = new ArrayBase;
// behavior of MyArray
var a2 = new MyArray(1, 2, 3, 0);
var a3 = a2.slice(1); // [2, 3, 0]
console.log(a3 instanceof MyArray); // true
console.log(a3 instanceof ArrayBase); // true
console.log(a3 instanceof Array); // true
console.log(a3.join(':')); // "2:3:0"
a3[5] = 1;
console.log(a3.length); // 6
a3.length = 2;
console.log(a3.toString()) // "2,3"
My questions:
How could the duplication that exists between the ArrayBase and MyArray constructors, be eliminated, while still preserving the behavior illustrated by the lines after the // behavior of MyArr line? Would the scheme work also at the time of subclassing MyArray?
(I'm aware of the arguments against building tall towers of inheritance, but whether they are good design or not, I want them to be at least soundly implemented.)
1If inheritance from Array were as I think it should be, it would not be necessary to implement ArrayBase.slice, but unfortunately the slice method that ArrayBase inherits from Array does not show the elementary OOP courtesy of returning an object of the same class as that of this.

Before answering your question, some comments on the code :-)
var arr = Array.prototype.constructor.apply(null, arguments);
Since Array.prototype.constructor === Array, don't duplicate this.
var ctor = arr.constructor = …
There's no reason to create a property here. If the constructor property is needed for anything, it should be inherited from that constructor's .prototype object.
arguments.callee;
Don't use deprecated arguments.callee! You know that it points to ArrayBase.
arr.__proto__ = ctor.prototype;
You probably know that __proto__ is nonstandard (and does especially not work in IE), but we need it here for the prototype injection technique. Still, don't forget this fact!
ArrayBase.prototype = new Array;
Do not use new for setting up inheritance! You don't want to invoke an initialiser (i.e. "constructor") here. Use Object.create instead.
Now, back to your question:
How can the duplication that exists between my ArrayBase and MyArray constructors be eliminated?
Actually you have used that concept already. Your ArrayBase.prototype.slice implementation works with every subclass - constructing instances of this.constructor again. You can use the same method for the ArrayBase constructor:
function ArrayBase() {
var arr = Array.apply(null, arguments);
var ctor = this.constructor;
arr.__proto__ = ctor.prototype;
return arr;
}
/* irrelevant for the answer, but helpful:
ArrayBase.prototype = Object.create(Array.prototype, {
constructor: {value: ArrayBase}
});
Object.keys(Array.prototype).forEach(function(k) {
if (typeof Array.prototype[k] != "function") return;
ArrayBase.prototype[k] = function() {
var arr = Array.prototype[k].apply(this, arguments);
if (Array.isArray(arr))
arr.__proto__ = Object.getPrototypeOf(this);
return arr;
};
});
*/
function MyArray() {
return ArrayBase.apply(this, arguments);
}
MyArray.prototype = Object.create(ArrayBase.prototype, {
constructor: {value: MyArray}
});

Related

Extends ES5 like in ES6 [duplicate]

I realize that, strictly speaking, this is not subclassing the array type, but will this work in the way one might expect, or am I still going to run into some issues with .length and the like? Are there any drawbacks that I would not have if normal subclassing were an option?
function Vector()
{
var vector = [];
vector.sum = function()
{
sum = 0.0;
for(i = 0; i < this.length; i++)
{
sum += this[i];
}
return sum;
}
return vector;
}
v = Vector();
v.push(1); v.push(2);
console.log(v.sum());
I'd wrap an array inside a proper vector type like this:
window.Vector = function Vector() {
this.data = [];
}
Vector.prototype.push = function push() {
Array.prototype.push.apply(this.data, arguments);
}
Vector.prototype.sum = function sum() {
for(var i = 0, s=0.0, len=this.data.length; i < len; s += this.data[i++]);
return s;
}
var vector1 = new Vector();
vector1.push(1); vector1.push(2);
console.log(vector1.sum());
Alternatively you can build new prototype functions on arrays and then just use normal arrays.
If you are consistent with naming the arrays so they all start with a lowercase v for example or something similar that clearly mark them aw vector and not normal arrays, and you do the same on the vector specific prototype functions, then it should be fairly easy to keep track of.
Array.prototype.vSum = function vSum() {
for(var i = 0, s=0.0, len=this.length; i < len; s += this[i++]);
return s;
}
var vector1 = [];
vector1.push(1); vector1.push(2);
console.log(vector1.vSum());
EDIT -- I originally wrote that you could subclass an Array just like any other object, which was wrong. Learn something new every day. Here is a good discussion
http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/
In this case, would composition work better? i.e. just create a Vector object, and have it backed by an array. This seems to be the path you are on, you just need to add the push and any other methods to the prototype.
Nowadays you could use subclassing with ES6 classes:
class Vector extends Array {
sum(){
return this.reduce((total, value) => total + value)
}
}
let v2 = new Vector();
v2.push(1);
v2.push(2);
console.log(v2.sum());
console.log(v2.length);
v2.length = 0;
console.log(v2.length);
console.log(v2);
Just another example of the wrapper. Having some fun with .bind.
var _Array = function _Array() {
if ( !( this instanceof _Array ) ) {
return new _Array();
};
};
_Array.prototype.push = function() {
var apContextBound = Array.prototype.push,
pushItAgainst = Function.prototype.apply.bind( apContextBound );
pushItAgainst( this, arguments );
};
_Array.prototype.pushPushItRealGood = function() {
var apContextBound = Array.prototype.push,
pushItAgainst = Function.prototype.apply.bind( apContextBound );
pushItAgainst( this, arguments );
};
_Array.prototype.typeof = (function() { return ( Object.prototype.toString.call( [] ) ); }());
#hvgotcodes answer has an awesome link. I just wanted to summerize the conclusion here.
Wrappers. Prototype chain injection
This seems to be the best method to extend array from the article.
wrappers can be used ... in which object’s prototype chain is augmented, rather than object itself.
function SubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
}
SubArray.prototype = new Array;
// Add custom functions here to SubArray.prototype.
SubArray.prototype.last = function() {
return this[this.length - 1];
};
var sub = new SubArray(1, 2, 3);
sub instanceof SubArray; // true
sub instanceof Array; // true
Unfortunally for me, this method uses arr.__proto__, unsupported in IE 8-, a browser I have to support.
Wrappers. Direct property injection.
This method is a little slower than the above, but works in IE 8-.
Wrapper approach avoids setting up inheritance or emulating length/indices relation. Instead, a factory-like function can create a plain Array object, and then augment it directly with any custom methods. Since returned object is an Array one, it maintains proper length/indices relation, as well as [[Class]] of “Array”. It also inherits from Array.prototype, naturally.
function makeSubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
// Add custom functions here to arr.
arr.last = function() {
return this[this.length - 1];
};
return arr;
}
var sub = makeSubArray(1, 2, 3);
sub instanceof Array; // true
sub.length; // 3
sub.last(); // 3
There is a way that looks and feels like prototypical inheritance, but it's different in only one way.
First lets take a look at one of the standard ways of implementing prototypical inheritance in javascript:
var MyClass = function(bar){
this.foo = bar;
};
MyClass.prototype.awesomeMethod = function(){
alert("I'm awesome")
};
// extends MyClass
var MySubClass = function(bar){
MyClass.call(this, bar); // <- call super constructor
}
// which happens here
MySubClass.prototype = Object.create(MyClass.prototype); // prototype object with MyClass as its prototype
// allows us to still walk up the prototype chain as expected
Object.defineProperty(MySubClass.prototype, "constructor", {
enumerable: false, // this is merely a preference, but worth considering, it won't affect the inheritance aspect
value: MySubClass
});
// place extended/overridden methods here
MySubClass.prototype.superAwesomeMethod = function(){
alert("I'm super awesome!");
};
var testInstance = new MySubClass("hello");
alert(testInstance instanceof MyClass); // true
alert(testInstance instanceof MySubClass); // true
The next example just wraps up the above structure to keep everything clean. And there is a slight tweak that seems at first glance to perform a miracle. However, all that is really happening is each instance of the subclass is using not the Array prototype as a template for construction, but rather an instance of an Array - so the prototype of the subclass comes hooked onto the end of a fully loaded object which passes the ducktype of an array - which it then copies. If you still see something strange here and it bothers you, I'm not sure that I can explain it better - so maybe how it works is a good topic for another question. :)
var extend = function(child, parent, optionalArgs){ //...
if(parent.toString() === "function "+parent.name+"() { [native code] }"){
optionalArgs = [parent].concat(Array.prototype.slice.call(arguments, 2));
child.prototype = Object.create(new parent.bind.apply(null, optionalArgs));
}else{
child.prototype = Object.create(parent.prototype);
}
Object.defineProperties(child.prototype, {
constructor: {enumerable: false, value: child},
_super_: {enumerable: false, value: parent} // merely for convenience (for future use), its not used here because our prototype is already constructed!
});
};
var Vector = (function(){
// we can extend Vector prototype here because functions are hoisted
// so it keeps the extend declaration close to the class declaration
// where we would expect to see it
extend(Vector, Array);
function Vector(){
// from here on out we are an instance of Array as well as an instance of Vector
// not needed here
// this._super_.call(this, arguments); // applies parent constructor (in this case Array, but we already did it during prototyping, so use this when extending your own classes)
// construct a Vector as needed from arguments
this.push.apply(this, arguments);
}
// just in case the prototype description warrants a closure
(function(){
var _Vector = this;
_Vector.sum = function sum(){
var i=0, s=0.0, l=this.length;
while(i<l){
s = s + this[i++];
}
return s;
};
}).call(Vector.prototype);
return Vector;
})();
var a = new Vector(1,2,3); // 1,2,3
var b = new Vector(4,5,6,7); // 4,5,6,7
alert(a instanceof Array && a instanceof Vector); // true
alert(a === b); // false
alert(a.length); // 3
alert(b.length); // 4
alert(a.sum()); // 6
alert(b.sum()); // 22
Soon we'll have class and the ability to extend native classes in ES6 but that may be a another year yet. In the mean time I hope this helps someone.
function SubArray(arrayToInitWith){
Array.call(this);
var subArrayInstance = this;
subArrayInstance.length = arrayToInitWith.length;
arrayToInitWith.forEach(function(e, i){
subArrayInstance[i] = e;
});
}
SubArray.prototype = Object.create(Array.prototype);
SubArray.prototype.specialMethod = function(){alert("baz");};
var subclassedArray = new SubArray(["Some", "old", "values"]);

How to avoid writing ugly code while dealing with object properties in javascript

I find that when I write javascript I encounter a situation where I am forced to write ugly code. This is due to my inability to reconcile the following two criteria:
1) Define data using shorthand, e.g. var data = { a: 1, b: { c: 2, d: 3 ... } }
2) Use a primitive operator to check for property existence
For example consider a function which returns the intersection of two Object instances' keys as an Array of the intersecting keys:
var intersection = function(obj1, obj2) {
var result = [];
for (var k in obj1) if (k in obj2) result.push(k);
return result;
};
That code looks quite nice. But unfortunately, it doesn't always work as expected! This is due to the inconsistency between for (x in y), and if (x in y): Using for (x in y) will only iterate over "own" properties (properties which return true for hasOwnProperty), while if (x in y) while apply for "own" and "non-own" properties!
If I call intersection like so:
var obj1 = { toString: 'hahaha' };
var obj2 = {};
var intersectingKeys = intersection(obj1, obj2);
I will wind up with intersectingKeys === [ 'toString' ]; Obviously this is not correct: an intersection operation involving an empty set (as obj2 appears to be) must return an empty set. While {} is clearly intended to be "empty", our problem is that ('toString' in {}) === true. This also applies to terms such as 'constructor', 'valueOf', as well as any new properties that are introduced to Object.prototype in the future.
In my opinion, if a native operator can provide iteration over keys, a native operator should be able to verify whether a key will appear in an iteration. It feels inconsistent and ugly, to me, to use a native operator for one, but a function call for the other. For that reason I dislike this fix:
var safeIntersection = function(obj1, obj2) {
var result = [];
for (var k in obj1) if (obj2.hasOwnProperty(k)) result.push(k);
return result;
};
If if (x in y) must be used, I only see one other possible solution: Ensure that the parameters passed to intersection have no properties at all, apart from properties explicitly defined by our code. In other words, ensure that we only work with prototype-less objects:
var obj1 = Object.create(null, {
toString: {
configurable: true,
enumerable: true,
writable: true,
value: 'hahaha'
}
});
var obj2 = Object.create(null, {});
var intersectingKeys = intersection(obj1, obj2);
Note that this code uses intersection, not safeIntersection, and still works because obj1 and obj2 are prototype-less. But the problem is, now data definition is really, really clunky! Look at how much code it takes to define an object with a single "toString" property. This approach prevents us from using javascript's beautiful object-shorthand. Even if we write a utility function to encompass prototype-less object creation, the definition of nested objects is still incredibly clunky:
// Utility function for prototype-less object definition
var obj = function(props) {
return Object.create(null, props.map(function(v) {
return {
writable: true,
configurable: true,
enumerable: true,
value: v
};
}));
};
// Now defining `obj1` looks ok...
var obj1 = obj({ toString: 'hahaha' });
// But for large, nested object definitions it's sooper ugly:
var big = obj({
a: 'a value',
b: 'b value',
moreProps: obj({
wheee: 'yay',
evenMoreProps: obj({
prop: 'propMeUp'
/* ... */
})
/* ... */
})
});
Javascript's object-definition shorthand is a huge perk of the language, and throwing it away by being forced to wrap all { ... } instances in a function call seems like a tremendous pity.
My ideal solution to this problem would involve converting the shorthand object constructor to produce prototype-less objects. Perhaps a global setting:
// Perhaps along with other global settings such as:
'use strict';
Error.stackTraceLimit = Infinity;
// We could also have:
Object.shorthandIncludesPrototype = false;
Although even if this solutions were available it would break tons and tons of pre-existing libraries. :(
How do I reconcile the following criteria???:
1) Write code that works
2) Use the primitive in operator to check for property existence
3) Define objects using typical shorthand
Perhaps it's impossible to meet all these criteria simultaneously. In that case, what are some of the next-best approaches for keeping code clean in these cases?
You could get first the own properties of the first object with Object.keys and filter with Object.hasOwnProperty the second object's keys.
function intersection(o1, o2) {
return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}
console.log(intersection({ a: 10, b: 20, e: 30 }, { a: 10, c: 20, d: 30 })); // ['a']
console.log(intersection({ toString: 'hahaha' }, {})); // []
So, you've set out three incompatible requirements. You say you have to use in. You say objects have to be defined as {} so they will have a prototype. But, you don't like the way in works. And, you want code that uses those, but works differently than they are designed. Those are your two choices.
There's NO answer with those requirements. in works the way it does. You can't change it. We can't change it. Yes, it works differently based on the context, but that's how it's implemented. Deal with that by finding a practical solution to a real problem or write your own language that works the way you want the language to work.
FYI, for a real-world solution you may want to consider a Map object for storing your data and use .get(), .has() and .set() on it. Simple, clear, works.
Prior Answer BEFORE question was massively edited
First off, you can just use Object.create(null); to create a prototype-less object. So, the only properties it will have are the ones you put there.
I'd suggest that rather than accessing a property directly off the object, you just create one reusable function that checks to see if the property name is valid before returning it:
function sendValidProperty(req, res) {
var propName = req.params.propertyName;
if (Object.prototype.hasOwnProperty.call(dataObject, propName) {
res.send(dataObject[propName]);
} else {
res.sendStatus(400);
}
}
router.get('/propertyOfObject/:propertyName', function(req, res) {
sendValidProperty(req, res);
});
Or, you could encapsulate a smaller piece of it:
function getValidProperty(obj, propName) {
return Object.prototype.hasOwnProperty.call(dataObject, propName) ? obj[propName] : null;
}
router.get('/propertyOfObject/:propertyName', function(req, res) {
let val = getValidProperty(dataObject, res.params.propertyName);
if (val !== null) {
res.send(val);
} else {
res.sendStatus(400);
}
});
In either case, you don't have to repeat the checking of a property. That's in the common, shared function. FYI, when you want to use object methods on a prototype-less object, you can use the form I show above: Object.prototype.hasOwnProperty.call(dataObject, propName) instead of dataObject.hasOwnProperty(propName).
One of the main rules of server development is to NEVER trust the input you get from a request. You always have to check it or sanitize it before using it. This whole question kind of sounds like you're trying to avoid doing that. You just can't shortcut checking input and have a fully reliable server.
I have 2 extra solutions that are probably worth mentioning here:
1) Recursive obj function
As I mentioned in the question, having an obj method which creates an un-prototyped value from a regular Object defined via shorthand gets really ugly when it needs to be applied all throughout a nested shorthand structure - but this problem can be somewhat solved if the obj method is recursive, so that it only needs to be applied to the root Object and not explicitly to every inner child property:
var obj = function(val) {
if (val.constructor === Object) {
var ret = Object.create(null);
for (var k in val) ret[k] = obj(val[k]);
} else if (val.constructor === Array) {
var ret = [];
for (var i = 0; i < val.length; i++) ret.push(obj(val[i]));
} else {
var ret = val;
}
return ret;
};
var thing = obj({
prop1: 'hello',
prop2: 'hi',
prop3: [
{ num: 1, val: 'lalala' },
{ num: 2, val: 'heehee' },
{ num: 3, val: 'hoho' },
],
really: {
deep: {
property: {
over: {
here: { val: 'soooo deep' }
}
}
}
}
});
console.log('toString' in thing);
console.log('toString' in thing.prop3[1]);
console.log('toString' in thing.really.deep.property.over.here);
Now it's important that all shorthand objects are wrapped in obj.
2) Clear out Object.prototype
I've found that chrome and node (v0.12.0) will allow me to delete all properties from Object.prototype. The prototype can even be restored at any point in the future if these removed properties are kept in memory:
var savedPrototype = {};
var removePrototype = function() {
savedPrototype = {};
var props = Object.getOwnPropertyNames(Object.prototype);
for (var i = 0; i < props.length; i++) {
savedPrototype[props[i]] = Object.prototype[props[i]];
delete Object.prototype[props[i]];
}
};
var restorePrototype = function() {
for (var k in savedPrototype) {
Object.prototype[k] = savedPrototype[k];
}
};
removePrototype();
var obj = { val: 'haha' };
console.log('toString' in obj); // false
restorePrototype();
console.log('toString' in obj); // true
As jfriend00 already wrote, the question proposes constraints that are fundamentally impossible in the current versions of JavaScript. The best we can do is write some abstractions within the limitations of the language.
We've already looked at some possibilities, such as using a wrapper function (like obj() in the question). Because "universal support" is not a criterion in the question, I'll propose one solution using ES6 Proxies. We can use the has() handler method of a Proxy to alter the behavior of the in operator so that it only considers own properties:
Object.prototype.own = function () {
return new Proxy(this, {
has: function (target, propertyName) {
return target.hasOwnProperty(propertyName);
}
});
};
document.writeln('toString' in {});
document.writeln('toString' in {}.own());
document.writeln('foo' in { foo: 1 });
document.writeln('foo' in { foo: 1 }.own());
This requires a modern browser (no IE; Edge OK) or Node 6.4.0+. Of course, adding members to the prototype of Object is a risky move. We can add additional handlers to the Proxy as needed, or rewrite the implementation for ES5 and below (but this requires significantly more code).
Compatibility concerns aside, this approach satisfies the requirements of the question: we can use object literals and the in operator, and the code is concise and readable. We just need to remember to call own() when we need it.
We can rewrite the intersection() method from the question using this convention:
Object.prototype.own = function () {
return new Proxy(this, {
has: function (target, propertyName) {
return target.hasOwnProperty(propertyName);
}
});
};
var intersection = function(obj1, obj2) {
var result = [];
for (var k in obj1.own()) if (k in obj2.own()) result.push(k);
return result;
};
document.writeln(intersection({ a: 1, b: 2, c: 3 }, { b: 2, c: 3, d: 4}));
Your question could be solved with some beautiful code.
ES5
var intersect = function(obj1, obj2) {
var keys1 = Object.keys(obj1);
var keys2 = Object.keys(obj2);
return keys1.filter(function(k) {
return keys2.indexOf(k) !== -1;
});
}
ES6
let intersect = (obj1, obj2) => {
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
return keys1.filter(k => keys2.includes(k));
}
This is the JavaScript way of solving such problems. It is way better, since you don't need to use any loop. The built-in methods like filter(), indexOf() or includes() would perform better than any loop.

Unable to extend javascript prototype

I am just playing around with the idea of subclassing with Javascript. I like to pretend that extending native objects (like Array, String etc) is a bad idea. This, however true, is completely out of my understanding as to why.
Having said that, let's get on with it.
What I'm trying to do is to extend Array (now, extend may not be right term for what I'm doing)
I want to create my new class MyArray and I want to have 2 methods on it. .add and .addMultiple.
So I implemented it like this.
function MyArray(){
var arr = Object.create(Array.prototype);
return Array.apply(arr, arguments);
}
MyArray.prototype = Array.prototype;
MyArray.prototype.add = function(i){
this.push(i);
}
MyArray.prototype.addMultiple = function(a){
if(Array.isArray(a)){
for(var i=0;i<a.length;i++){
this.add(a[i]);
}
}
}
This works correctly, but if I do
console.log(Array.prototype.addMultiple );
console.log(Array.prototype.add);
I get [Function] and [Function].
So this means my code is modifying the native Array object. Something that I am trying to avoid. How do I change this code in a way that those two console.logs will give me undefined but I am still able to use native Array.prototype methods like .push?
TIA
You should setup proper prototypes chain:
function MyArray(){
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype);
Object.create just creates new object with specified prototype, so after this operation following is true:
MyArray.prototype !== Array.prototype; // true
Object.getPrototypeOf(MyArray.prototype) === Array.prototype; // true
This:
MyArray.prototype = Array.prototype;
results in MyArray.prototype pointing to the same object as Array.prototype. So everything you do to MyArray.prototype after that will also be done to Array.prototype.
A way to solve this is to instead store a shallow copy of Array's prototype in MyArray:
MyArray.prototype = clone(Array.prototype);
I copied that from here:
Copy prototype for inheritance?
use class extension
class MyArray extends Array {
add(i) {
this.push(i);
return this;
}
addMultiple(a) {
if (Array.isArray(a)) {
for (var i = 0; i < a.length; i++) {
this.add(a[i]);
}
}
return this;
}
}
var test = new MyArray();
test.addMultiple([1,2,3,4,5]).add(6).add(7);
console.log(test, test.indexOf(6));
Object.create(Array.prototype);
This just creates a new object and return the objects.
So as per your scenarios, you have just created array object and added some methods to your array object - MyArray. It will affect the native Array.
You're just modifying your cloned object.

Custom array object

I'm new to prototyping and instantiations and therefore had a question :
How can I create a function that constructs a new array that also has some properties added with prototype but without modifying the default Array function ?
For example :
function Cool_Object() {
this = new Array() // Construct new array.
//This is only for the example. I know you can't do that.
}
Cool_Object.prototype.my_method = function() {
// Some method added
};
So, if you call :
var myObject = new Cool_Object();
myObject would be an array and have a method called "my_method" (which actually calls a function).
But the default Array object would be intact.
Thanks in advance !
You've got it a bit backwards. Just use Array.prototype as your custom object's prototype.
function Cool_Object() {
this.my_method = function () {
return 42;
}
}
Cool_Object.prototype = Array.prototype;
var foo = new Cool_Object();
foo.my_method(); // 42
foo.push(13);
foo[0]; // 13
You can get both Array.prototype and my_method on Cool_Object's prototype, without modifying Array.prototype, by introducing an intermediate type:
function Even_Cooler() {}
Even_Cooler.prototype = Array.prototype;
function Cool_Object() {}
Cool_Object.prototype = new Even_Cooler();
Cool_Object.prototype.my_method = function () {
return 42;
}
You can't just assign to this, it doesn't work and throws a ReferenceError. Just make Cool_Object extend Array.
One way to do that:
var Cool_Object = Object.create(Array.prototype);
Cool_Object.my_method = function() {
// Some method added
};
Then create further objects with
var obj = Object.create(Cool_Object);
Use an array as the function's prototype, so that your new type "inherits" from Array, and then introduce new methods in the prototype:
function CustomArray() {}
CustomArray.prototype = [];
// introduce a new method to your custom array type
CustomArray.prototype.total = function() {
return this.reduce(function(ret, el) {
return ret+el;
}, 0);
};
// introduce another new method to your custom array type
CustomArray.prototype.arithmetiMean = function() {
return this.total()/this.length;
};
Alternately you could introduce those methods in new instances:
function CustomArray() {
// introduce a new method to your custom array object
this.total = function() {
return this.reduce(function(ret, el) {
return ret+el;
}, 0);
};
// introduce another new method to your custom array object
this.arithmetiMean = function() {
return this.total()/this.length;
};
}
CustomArray.prototype = [];
var arr = new CustomArray();
arr.push(1); // push is an array-standard method
arr.push(2);
arr.push(3);
arr.push(4);
arr.push(5);
arr.push(6);
arr.push(7);
arr.push(8);
arr.push(9);
arr.push(10);
console.log(arr.arithmetiMean());
function PseudoArray() {
};
PseudoArray.prototype = Object.defineProperties(Object.create(Array.prototype), {
constructor: {value:PseudoArray}
})
Adding this for reference, since Object.create is supported in most browsers these days, a good way to make your own array object would be like this:
function MyCustomArray(){
}
MyCustomArray.prototype = $.extend(Object.create(Array.prototype), {
/* example of creating own method */
evenonly : function(){
return this.filter(function(value){return (value % 2 == 0);});
},
/* example for overwriting existing method */
push : function(value){
console.log('Quit pushing me around!');
return Array.prototype.push.call(this, value);
}
});
var myca = new MyCustomArray();
myca instanceof MyCustomArray /*true*/
myca instanceof Array /*true*/
myca instanceof Object /*true*/
myca.push(1); /*Quit pushing me around!*/
myca.push(2); /*Quit pushing me around!*/
myca.push(3); /*Quit pushing me around!*/
myca.push(4); /*Quit pushing me around!*/
myca.push(5); /*Quit pushing me around!*/
myca.push(6); /*Quit pushing me around!*/
myca.length; /*6*/
myca.evenonly() /*[2, 4, 6]*/
Using jQuery's $.extend, because it's convenient to keep code structured, but there's no need for it, you could do this instead:
MyCustomArray.prototype = Object.create(Array.prototype);
MyCustomArray.prototype.push = function(){...}
I much prefer defining the methods on the prototype rather than putting them inside the constructor. It's cleaner and saves your custom array object from being cluttered with unnecessary functions.

Crockford-style prototypal pattern gotcha; looking for an elegant solution

I often use Crockford's prototypal pattern when writing JavaScript programs. I thought I understood all the "gotchas" involved, but I discovered one I didn't think about before. I'd like to know if anyone has a best practice for handling it.
Here's a simple example:
// Here's the parent object
var MyObject = {
registry: {},
flatAttribute: null,
create: function () {
var o, F = function () {};
F.prototype = this;
o = new F();
return o;
}
};
// instance is an empty object that inherits
// from MyObject
var instance = MyObject.create();
// Attributes can be set on instance without modifying MyObject
instance.flatAttribute = "This is going to be applied to the instance";
// registry doesn't exist on instance, but it exists on
// instance.prototype. MyObject's registry attribute gets
// dug up the prototype chain and altered. It's not possible
// to tell that's happening just by examining this line.
instance.registry.newAttribute = "This is going to be applied to the prototype";
// Inspecting the parent object
// prints "null"
console.log(MyObject.flatAttribute);
// prints "This is going to be applied to the prototype"
console.log(MyObject.registry.newAttribute);
I want to feel safe that any changes that appear to be made to the instance don't propagate up the inheritance change. This is not the case when the attribute is an object and I'm setting a nested property.
A solution is to re-initialize all object attributes on the instance. However, one of the stated advantages of using this pattern is removing re-initialization code from the constructor. I'm thinking about cloning all the object attributes of the parent and setting them on the instance within the create() function:
{ create: function () {
var o, a, F = function () {};
F.prototype = this;
o = new F();
for (a in this) {
if (this.hasOwnProperty(a) && typeof this[a] === 'object') {
// obviously deepclone would need to be implemented
o[a] = deepclone(this[a]);
}
}
return o;
} };
Is there a better way?
There is a very simple solution to ensuring that they are instance variables only, which is to use the this keyword in the constructor.
var MyObject = {
flatAttribute: null,
create: function () {
var o, F = function () {
this.registry = {}
};
F.prototype = this;
o = new F();
return o;
}
};
this ensures that all properties of "instance.registry.*" are local to the instance because the lookup order for javascript opjects is as follows.
object -> prototype -> parent prototype ...
so by adding a variable to the instance in the constructor function named "registry" that will always be found first.
another solution, which I think is more elegant is to not use crockford's (java style) constructors and use a layout that reflects javascripts object system more naturally. most of those gotchas are from the misfit between practice and language.
// instance stuff
var F = function () {
this.registry = {}
};
F.prototype = {
// static attributes here
flatAttribute: null,
methodA: function(){
// code here 'this' is instance object
this.att = 'blah';
}
};
var instanceA = new F();
instanceA.registry['A'] = 'hi';
var instanceB = new F();
instanceB.registry['B'] = 'hello';
instanceA.registry.A == 'hi'; // true
instanceB.registry.B == 'hello'; // true
F.prototype.registry == undefined; // true
Will this give you the expected result? Here I am not using an Object literal, but an instantly instantiated constructor function for the parent object (Base):
var Base = ( function(){
function MyObject(){
this.registry = {},
this.flatAttribute = null;
if (!MyObject.prototype.create)
MyObject.prototype.create = function(){
return new this.constructor();
};
}
return new MyObject;
} )(),
// create 2 instances from Base
instance1 = Base.create(),
instance2 = Base.create();
// assign a property to instance1.registry
instance1.registry.something = 'blabla';
// do the instance properties really belong to the instance?
console.log(instance1.registry.something); //=> 'blabla'
console.log(instance2.registry.something === undefined); //=> true
But it's all a bit virtual. If you don't want to use the new operator (I think that was te whole idea of it), the following offers you a way to do that without the need for a create method :
function Base2(){
if (!(this instanceof Base2)){
return new Base2;
}
this.registry = {},
this.flatAttribute = null;
if (!Base2.prototype.someMethod){
var proto = Base2.prototype;
proto.someMethod = function(){};
//...etc
}
}
//now the following does the same as before:
var instance1 = Base2(),
instance2 = Base2();
// assign a property to instance1.registry
instance1.registry.something = 'blabla';
// do the instance properties really belong to the instance?
console.log(instance1.registry.something); //=> 'blabla'
console.log(instance2.registry.something === undefined); //=> true
Example in a jsfiddle
I always like to keep in mind that object.Create is one option, and not the only way of achieving non-classical inheritance in javascript.
For myself, I always find that Object.create works best when I want to inherit elements from the parent objects prototype chain (i.e. methods that I'd like to be able to apply to the inheriting object).
--
For simple "Own Property" inheritance, Object.create is largely unnecessary. When I want to inherit own properties, i prefer to use the popular Mixin & Extend patterns (which simply copy one object's own properties to another, without worrying about prototype or "new").
In the Stoyan Stefanov book "Javascript Patterns" he gives an example of a deep extend function that does what you're looking for recursively, and includes support for properties that are arrays as well as standard key/value objects:
function extendDeep(parent, child){
var i,
toStr = Object.prototype.toString,
astr = "[object Array]";
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === "object") {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
If you're using jQuery, jQuery.extend() has an optional "deep" argument that lets you extend an object in near-identical fashion.
i think you're using prototypal inheritance to simulate a classic, Object Oriented inheritance.
What are you trying to do is to stop the prototype method lookup which limits its expressiveness, so why using it? You can achieve the same effect by using this functional pattern:
var MyObject = function() {
// Declare here shared vars
var global = "All instances shares me!";
return {
'create': function() {
var flatAttribute;
var register = {};
return {
// Declare here public getters/setters
'register': (function() {
return register;
})(),
'flatAttribute': (function() {
return flatAttribute;
})(),
'global': (function() {
return global;
})()
};
}
};
}();
var instance1 = MyObject.create();
var instance2 = MyObject.create();
instance1.register.newAttr = "This is local to instance1";
instance2.register.newAttr = "This is local to instance2";
// Print local (instance) var
console.log(instance1.register.newAttr);
console.log(instance2.register.newAttr);
// Print global var
console.log(instance1.global);
console.log(instance2.global);
Code on jsFiddle

Categories

Resources