I'm trying to extend the array object but I'm getting so confused with the inheritance part of it. Let me demonstrate it with an example:
function Class1() {
return Array.apply(null, arguments);
};
function Class2() {
Array.apply(null, arguments);
return 'ignored?';
};
Class1.prototype = Object.create(Array.prototype);
Class2.prototype = Object.create(Array.prototype);
var list1 = new Class1('one');
list1.push(1);
console.log("list1: ", list1);
//prints ["one", 1]
console.log(Array.isArray(list1), list1 instanceof Class1);
//prints isArray: true, instanceof Class1: false
var list2 = new Class2('two');
list2.push(2);
console.log("list2: ", list2);
//prints [2] only.. 'two' from constructor is ignored?
console.log(Array.isArray(list2), list2 instanceof Class2);
//prints isArray:false, instanceof Class2: true
//return value in function Class2 has no effect
My questions are:
Why does the return value of Class1 makes the list1 an instance of Array but the return value of Class2 is ignored?
What is the correct way so that an Object will be an instanceof Class test and also pass Array.isArray(obj) test?
Thanks for the help. This Javascript inheritance is driving me nuts.
Regarding:
'two' from constructor is ignored?
Note that when the Array constructor is called with exactly one argument, it's treated as setting the length. So:
var list2 = new Class2('two');
calls (effectively):
Array('two');
which creates a new array. If the single argument is not a number, it's ignored and an empty array is created (see step 7 of the algorithm). This is one reason that it is recommended to use an Array literal or Array initializer rather than the constructor.
Also, instanceOf is unreliable. All it does is check if an object has the constructor's prototype somewhere on its [[Prototype]] chain.
Related
I am wondering what is the difference between Array.prototype.isPrototypeOf and Array.isPrototypeOf I thought it should work the same because I thought it will refer to the same method isPrototypeOf but It's look like I was mistaken. Can anybody explain to me why this work like that ?
const exampleArray = [1, 2, 3];
console.log(Array.prototype.isPrototypeOf(exampleArray));
console.log(Array.isPrototypeOf(exampleArray)); // Why this statement returns false ?
Those are both references to
Object.prototype.isPrototypeOf(), which checks to see if the object it's called on is in the prototype chain of the passed argument.
For the exampleArray, the prototype chain is this:
Object.prototype <- Array.prototype <- exampleArray instance
See snippet:
const exampleArray = [1, 2, 3];
console.log(
Object.getPrototypeOf(exampleArray) === Array.prototype,
Object.getPrototypeOf(Array.prototype) === Object.prototype
);
The Array constructor function - window.Array - is not in the prototype chain, so isPrototypeOf returns false.
The Array constructor function would only have isPrototypeOf return true if a class extended Array, or if it was set to be the internal prototype of a new object via Object.create, eg:
class ExtendedArray extends Array {}
console.log(Array.isPrototypeOf(ExtendedArray));
const somethingWeird = Object.create(Array);
console.log(Array.isPrototypeOf(somethingWeird));
For completeness, the Array constructor function - being a function - inherits from Function.prototype, which inherits from Object.prototype:
console.log(
Object.getPrototypeOf(Array) === Function.prototype,
Object.getPrototypeOf(Function.prototype) === Object.prototype
);
I have created an array like prototype:
function METracker() {}
METracker.prototype = Object.create(Array.prototype);
METracker.prototype.myMethod = function(aStd) {
return true;
};
now i create an instance:
var aInst = new METracker('a', 'b', 'c');
Now I want to clone it so I do:
var cloneInst = aInst.slice();
however cloneInst no longer has the method .myMethod is there a way to keep the prototype on the clone?
Thanks
If you're going to create your own array-alike, the trick is to extend an array instance, not the array prototype:
function MyAry() {
var self = [];
[].push.apply(self, arguments);
// some dark magic
var wrap = 'concat,fill,filter,map,slice';
wrap.split(',').forEach(function(m) {
var p = self[m];
self[m] = function() {
return MyAry.apply(null, p.apply(self, arguments));
}
});
// add your stuff here
self.myMethod = function() {
document.write('values=' + this.join() + '<br>');
};
return self;
}
a = new MyAry(11,44,33,22);
a.push(55);
a[10] = 99;
a.myMethod()
b = a.sort().slice(0, 4).reverse();
b.myMethod();
Basically, you create a new array (a normal array, not your object), wrap some Array methods so that they return your object instead of generic arrays, and add your custom methods to that instance. All other array methods and the index operation keep working on your object, because it's just an array.
I have created an array like prototype:
No, you haven't.
function METracker() {}
METracker.prototype = Object.create(Array.prototype);
METracker.prototype.myMethod = function(aStd) {
return true;
};
The METracker constructor does nothing at all, it will just return a new Object with Array.prototype on its [[Prototype]] chain.
var aInst = new METracker('a', 'b', 'c');
Just returns an instance of METracker, it has no data since the constructor doesn't do anything with the arguments passed. Assigning Array.prototype to the inheritance chain doesn't mean the Array constructor is invoked.
var cloneInst = aInst.slice();
Note that callling slice() on aInst just returns a new, empty array. aInst doesn't have a length property, so the algorithm for slice has nothing to iterate over. And even if aInst had properties, slice will only iterate over the numeric ones that exist with integer values from 0 to aInst.length - 1.
If you want to create a constructor that creates Array–like objects, consider something like:
function ArrayLike() {
// Emulate Array constructor
this.length = arguments.length;
Array.prototype.forEach.call(arguments, function(arg, i) {
this[i] = arg;
}, this);
}
ArrayLike.prototype = Object.create(Array.prototype);
var a = new ArrayLike(1,2,3);
document.write(a.length);
document.write('<br>' + a.join());
The above is just play code, there is a lot more to do. Fixing the length issue isn't easy, I'm not sure it can be done. Maybe there needs to be a private "fixLength" method, but methods like splice need to adjust the length and fix indexes as they go, so you'll have to write a constructor that emulates the Array constructor and many methods to do re–indexing and adjust length appropriately (push, pop, shift, unshift, etc.).
In my investigation into making Array-like objects, I made this function,
Array2 = function(){
var out = [];
Object.defineProperty(out, 'prototype', { value : Array2.prototype }); // store a reference
out.__proto__ = Array2.prototype; // necessary as Array uses __proto__ and not prototype
if(arguments.length > 1) Array.prototype.push.apply(out, arguments); // re-implement constructor's
else if(arguments.length === 1) out.length = arguments[0]; // argument handling behaviour
return out;
};
// allow for normal prototyping behaviour
Array2.prototype = [];
Object.defineProperty(Array2.prototype, 'constructor', { value : Array2 });
and noticed that calling Array2() was returning the same as calling new Array2(), which isn't what I was expecting, so I considered a similar function for integers
Int = function(n){
var out = ~~n;
out.prototype = Int.prototype;
out.__proto__ = Int.prototype;
this.value = out; // added to check value when working as object
return out;
};
Int.prototype = 0;
Int.prototype.constructor = Int;
this time, Int returns a normal instance of a Number (__proto__ and prototype as for any number literal) and new Int returns an "Int" Object with Empty as __proto__ and undefined for prototype, with the number available through .value, same as calling without new.
Why are these very similar functions acting so differently, and why is new resulting in the for the first one? It is most likely something obvious I've overlooked.
Only tested in Google Chrome.
Actually, your Array2 function return real Arrays instead of only Array-like objects, this does not change when setting the [[prototype]] to an object that inherits from Array.prototype (altough you should not have created an array using [], but a plain object using Object.create(Array.prototype).
Your function Int has several problems.
out is a primitive number value, and has no properties. When assigning some, it will be implicitly casted to a Number object, which is discarded rightafter. The same problem with the "constructor" property on Int.prototype = 0.
Also, you can't use primitive values like 0 as prototype objects. When creating a new Int instance, it will inherit from the default Object.prototype as 0 is not of type "object". I'm not sure what happens when assigning such to the non-standard __proto__ property, but I guess it just fails.
Use this instead:
function Int(n){
var out = ~~n;
this.valueOf = function(){ return out; };
return out; // when not used as a constructor, return int-casted number
};
Int.prototype = Object.create(Number.prototype, {
constructor:{value:Int}
});
I want to create a constructor function in javascript, which has a .prototype property and can be used with the new keyword to create new objects that have this property in their protochains. I also would like this object to subclass an Array.
I have been able to create an object that subclasses an Array (all needed functionality works)
I have not figured out how to make this object act as a function, so that it can be used as a constructor.
SubArray = function() {this.push.apply(this, arguments);};
SubArray.prototype = Object.create(Array.prototype);
SubArray.prototype.constructor = SubArray;
SubArray.prototype.last = function(){return this[this.length -1]};
var arr = new SubArray(0); // [0]
arr.push(1,2,3); // [0,1,2,3]
console.log(arr, arr.length); // [0,1,2,3], 4
arr.length = 2;
console.log(arr, arr.length); // [0,1], 2
console.log(arr.last()); // 2
console.log(arr instanceof Array); // true
console.log(arr instanceof SubArray); // true
I have read that by adding certain keys to the arr object it can be used as a constructor function.
I believe I would have to do something like this.
var arrayFunction = new SubArray(0); // [0]
arrayFunction.prototype = {
constructor: arrayFunction,
//shared functions
};
arrayFunction.call = function(){//this would be the constructor?};
arrayFunction.constructpr = function(){//I remember seeing this as well, but I can't find the original source where I saw this};
I would really appreciate any insight into how this can be done, thanks in advance for your help
I recall John Resig tried to subclass an Array for Jquery, but found it not possible.
Okay, I'm a little stumped. I'm probably missing something blatantly obvious but apparently I just can't see the forest for the trees:
I'm trying to call a JavaScript function that expects its parameter to be an array, i.e. it checks if (arg instanceof Array)... Unfortunately, I (or Rhino) just can't seem to create such an array:
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
String src = "function f(a) { return a instanceof Array; };";
cx.evaluateString(scope, src, "<src>", 0, null);
Function f = (Function) scope.get("f", scope);
Object[] fArgs = new Object[]{ new NativeArray(0) };
Object result = f.call(cx, scope, scope, fArgs);
System.out.println(Context.toString(result));
Context.exit();
And alas, result is false.
What am I missing here?
Edit:
Just a little more information: both [] instanceof Array and new Array() instanceof Array return true as one would expect. If I add elements to the array they show up in the JavaScript code with the right indices (numeric, starting from zero):
NativeArray a = new NativeArray(new Object[]{ 42, "foo" });
When output using this JavaScript function:
function f(a) {
var result = [];
result.push(typeof a);
for (var i in a) {
result.push(i + ' => ' + a[i]);
}
return result.join('\\n');
}
The result is:
object
0 => 42
1 => foo
So it works. Except that I want a 'real' array :)
Almost forgot: Object.prototype.toString.call(a) returns [object Array]
Okay, that's the crucial information. That tells us that the array really is an array, it's just that it's being initialized by an Array constructor in a different scope than the one that the function is testing for, exactly as though you were testing an array from one window against another window's Array constructor in a browser-based app. E.g., there's a scope problem.
Try replacing
Object[] fArgs = new Object[]{ new NativeArray(0) };
with
Object[] fArgs = new Object[]{ cx.newArray(scope, 0) };
...to ensure the correct Array constructor is used. Because you've gone directly to the NativeArray constructor, you've bypassed ensuring that its scope is right, and so the array object's constructor is an Array constructor, but not the same Array constructor as the one on the global object the function sees.
For those who are intentionally creating a different subclass of the array implementation, and therefore can't use cx.newArray, what you can do is:
add this line
ScriptRuntime.setBuiltinProtoAndParent(fArgs, scope, TopLevel.Builtins.Array);