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.
Related
I was creating a component and was trying to break my implementation. The idea is to not allow user to manipulate the exposed properties.
The implementation was like this:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data; },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, setting the property is handled but user is still able to mutate it using .push. This is because I'm returning a reference.
I have handled this case like:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data.slice(); },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, I'm returning a new array to solve this. Not sure how it will affect performance wise.
Question: Is there an alternate way to not allow user to mutate properties that are of type object?
I have tried using writable: false, but it gives me error when I use it with get.
Note: I want this array to mutable within class but not from outside.
Your problem here is that you are effectively blocking attempts to modify MyClass. However, other objects members of MyClass are still JavaScript objects. That way you're doing it (returning a new Array for every call to get) is one of the best ways, though of course, depending of how frequently you call get or the length of the array might have performance drawbacks.
Of course, if you could use ES6, you could extend the native Array to create a ReadOnlyArray class. You can actually do this in ES5, too, but you lose the ability to use square brackets to retrieve the value from a specific index in the array.
Another option, if you can avoid Internet Explorer, is to use Proxies (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
With a proxy, you can trap calls to get properties of an object, and decide what to return or to do.
In the example below, we create a Proxy for an array. As you see in the handler, we define a get function. This function will be called whenever the value of a property of the target object is accessed. This includes accessing indexes or methods, as calling a method is basically retrieving the value of a property (the function) and then calling it.
As you see, if the property is an integer number, we return that position in the array. If the property is 'length' then we return the length of the array. In any other case, we return a void function.
The advantage of this is that the proxyArray still behaves like an array. You can use square brackets to get to its indexes and use the property length. But if you try to do something like proxyArray.push(23) nothing happens.
Of course, in a final solution, you might want decide what to do based on which
method is being called. You might want methods like map, filter and so on to work.
And finally, the last advantage of this approach is that you keep a reference to the original array, so you can still modify it and its values are accessible through the proxy.
var handler = {
get: function(target, property, receiver) {
var regexp = /[\d]+/;
if (regexp.exec(property)) { // indexes:
return target[property];
}
if (property === 'length') {
return target.length;
}
if (typeof (target[property]) === 'function') {
// return a function that does nothing:
return function() {};
}
}
};
// this is the original array that we keep private
var array = [1, 2, 3];
// this is the 'visible' array:
var proxyArray = new Proxy(array, handler);
console.log(proxyArray[1]);
console.log(proxyArray.length);
console.log(proxyArray.push(32)); // does nothing
console.log(proxyArray[3]); // undefined
// but if we modify the old array:
array.push(23);
console.log(array);
// the proxy is modified
console.log(proxyArray[3]); // 32
Of course, the poblem is that proxyArray is not really an array, so, depending on how you plan to use it, this might be a problem.
What you want isn't really doable in JavaScript, as far as I'm aware. The best you can hope for is to hide the data from the user as best you can. The best way to do that would be with a WeakMap
let privateData = new WeakMap();
class MyClass {
constructor() {
privateData.set(this, {
data: []
});
}
addEntry(entry) {
privateData.get(this).data.push(entry);
}
getData() {
return privateData.get(this).data.concat();
}
}
So long as you never export privateData don't export from the module, or wrap within an IIFE etc.) then your MyClass instances will be able to access the data but external forces can't (other than through methods you create)
var myInstance = new MyClass();
myInstance.getData(); // -> []
myInstance.getData().push(1);
myInstance.getData(); // -> []
myInstance.addEntry(100);
myInstance.getData(); // -> [100]
I am trying to loop through an array of gameobjects and call their update methods.
Gameobjects can have different update implementations (eg: update of enemy is different from update of friend), so I created an prototype inheritance chain. But I can't get it to work: while looping through all objects I don't seem to be able to call their update methods: the compiler says they don't exist. So my question is: is it possible in Javascript to loop trough an array of objects that share the same base class and call a method on them that can be overwritten by different sub-classes?
This is what I have so far, don't know where I went wrong...:
//base gameobject class
function GameObject(name) {
this.name = name
};
GameObject.prototype.update = function(deltaTime) {
throw new Error("can't call abstract method!")
};
//enemy inherits from gameobject
function Enemy() {
GameObject.apply(this, arguments)
};
Enemy.prototype = new GameObject();
Enemy.prototype.constructor = Enemy;
Enemy.prototype.update = function(deltaTime) {
alert("In update of Enemy " + this.name);
};
var gameobjects = new Array();
// add enemy to array
gameobjects[gameobjects.length] = new Enemy("weirdenemy");
// this doesn't work: says 'gameobject doesn't have update method'
for (gameobject in gameobjects) {
gameobject.update(1); // doesn't work!!
}
Its not a problem with your Inheritance chain, but with this construct
for(gameobject in gameobjects){
gameobject.update(1); // doesn't work!!
}
When you iterate an Array with for..in, the variable will have the index values only. So, gameobject will have 0, 1.. like that, in every iteration. It is not recommended to use for..in to iterate an Array.
You might want to use, Array.prototype.forEach, like this
gameobjects.forEach(function(gameObject) {
gameObject.update(1);
});
When you iterate through an array with for ... in, the values of the loop variable will be the keys to the array, not the values.
You really shouldn't iterate through arrays that way anyway:
for (var i = 0; i < gameobjects.length; ++i)
gameobjects[i].update(1);
Try this, it works for me: =)
gameobjects.forEach(function(gameobject){
gameobject.update(1); // doesn't work!!
});
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}
});
I have a javascript object cloning question. I'd like to be able to clone object methods that have been altered from those defined by the object prototype, or added to the object after instantiation. Is this possible?
The setting here is a javascript 'class' defined by me, so I'm fine with writing a clone method specific to my object class. I just can't figure out how to copy methods.
Example:
function myObject( name, att, dif ) {
/* 'privileged' methods */
this.attribute = function(newAtt) { // just a getter-setter for the 'private' att member
if(newAtt) { att = newAtt; }
return att;
}
// 'public' members
this.printName = name;
}
myObject.prototype.genericMethod = function() {
// does what is usually needed for myObjects
}
/* Create an instance of myObject */
var object153 = new myObject( '153rd Object', 'ABC', 2 );
// object153 needs to vary from most instances of myObject:
object153.genericMethod = function() {
// new code here specific to myObject instance object153
}
/* These instances become a collection of objects which I will use subsets of later. */
/* Now I need to clone a subset of myObjects, including object153 */
var copyOfObject153 = object153.clone();
// I want copyOfObject153 to have a genericMethod method, and I want it to be the one
// defined to be specific to object153 above. How do I do that in my clone() method?
// The method really needs to still be called 'genericMethod', too.
In your clone function, test each method on the object to see if it is equal to the same method on the object's constructor's prototype.
if (obj[method] != obj.constructor.prototype[method])
clone[method] = obj[method];
It sounds like you just want a shallow copy. However beware of that objects are shared among instances since we're not deep copying.
function clone(obj) {
var newObj = new obj.constructor();
for (var prop in obj) {
newObj[prop] = obj[prop];
}
return newObj;
}
var cloned = clone(object153);
A different syntax would be
myObj.prototype.clone = function() {
var newObj = new this.constructor();
for (var prop in this) {
newObj[prop] = this[prop];
}
return newObj;
}
var cloned = object153.clone();
Try it out and see if it works for you, it's still hard to tell what you're doing. If it doesn't, explain why, then I can better understand the problem.
I am trying to build small functions to simulate Class related functionalities (ok, I know about frameworks).
In the following example, the code almost work, it fails on deep cloning, somehow the Test.options.* fails to clone/copy, in all created objects options.* is the same reference, any idea what I have been doing wrong?
//* class lib *//
function clone(o) {
var tmp = new o.constructor();
tmp.__proto__ = o;
return tmp;
};
var Class = function(o) {
var tmp = new Function();
tmp.prototype = clone(o);
return tmp;
};
//*/
//* object schema *//
var Test = new Class({
deep: "yes",
options: {
inside: true
}
});
//*/
//* object Test 1 *//
var t1 = new Test();
console.log(t1.deep); // "yes"
t1.deep = "no";
console.log(t1.deep); // "no"
console.log(t1.options.inside); // true
t1.options.inside = false;
console.log(t1.options.inside); // false
//*/
//* object Test 2 *//
var t2 = new Test();
console.log(t2.deep); // "yes"
console.log(t2.options.inside); // should be true but I get false
Like this?
http://overset.org/2007/07/11/javascript-recursive-object-copy-deep-object-copy-pass-by-value/
As noted there, if you're using jQuery, you already have this functionality:
http://docs.jquery.com/Utilities/jQuery.extend
I know this is not a complete answer but consider that proto is only available in mozilla engines (I am assuming you are using Rhino here).
Logically your clone is actually wrong. You are not understanding prototypes here.
A prototype is an object to look up a property in if the parent of the prototype does not contain it (think about it that way). Which means that the following code IS true in your clone structure.
var t1 = new Test(); // test object created
var t2 = new Test(); // test object 2 created
t1.options.inside = false;
t2.options.inside; // false
t1.options.inside = true;
t1.options = { inside: false };
t1.options.inside; // false
t2.options.inside; // true
This is because options is a shared reference in the prototype. Prototype never clones, it only allows your object to define a property, and thus HIDING the prototype's value.
A true clone will do something like
for(var i in o) {
clone[i] = clone(o[i]); // deep cloning here
clone[i] = o[i]; // shallow cloning
}
This is of course simplified because there are issues with cloning dates, strings, etc. In general cloning is not a simple task.
Check out http://javascript.crockford.com/ it covers lots of object creation mechanisms and describes prototypes well.