Good day! I have this code:
function MyArray() {}
MyArray.prototype.length = 0;
(function() {
var methods = ['push', 'pop', 'shift', 'unshift',
'slice', 'splice', 'join'];
for (var i = 0; i < methods.length; i++) (function(name) {
MyArray.prototype[ name ] = function() {
return Array.prototype[ name ].apply(this, arguments);
};
})(methods[i]);
})();
I need explanation. I understood that "methods" is array of real methods, which just "exported" to our new class. But, what is this: MyArray.prototype.length = 0; ? Author create new prototype property and assign it zero. And later use this new property!
var mine = new MyArray();
mine.push(1, 2, 3);
assert(mine.length == 3 ...
.....
How it is work? "length" have not instantiation in code above!
Its getting initialized at zero so that if you never call any of its functions, it will return zero (like a real array) and not undefined. Also it needs to start at zero so that the methods update it correctly. in your example, length its 3 because the push method did so.
You can't really subclass Array http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/
So if you create an instance of MyArray you can't do: MyArr[0]=...
You can wrap an array inside MyArray and take advantage of the Array functions:
var MyArray=function() {
this.arr=[];
[].push.apply(this.arr,arguments);
//following doesn't work in older browsers
Object.defineProperty(this,"length",{
get:function(){return this.arr.length;},
enumerable:true,
configurable:true
});
}
MyArray.prototype.valueOf=function(){return this.arr;};
(function() {
var methods = ['push', 'pop', 'shift', 'unshift',
'slice', 'splice', 'join'],i=methods.length
while(--i!==-1){
;(function(name) {
MyArray.prototype[ name ] = function() {
console.log(arguments);
return Array.prototype[ name ].apply(this.arr, arguments);
};
}(methods[i]));
}
}());
var mArr1=new MyArray(1,2,3);
console.log(mArr1.slice(0,1));
//you cannot do this: myArr1[0]=22;
Related
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"]);
I'm trying to make an array object that can set itself.
Something like this...
array = new Array(10)
array.someMethod = function () {
this = new Array(20)
}
Except this doesn't work because this can't be set like that.
More or less I want an object that I use array[index] on.
Seems to me this could be accomplished by using an object and storing the array in one of the object's properties. I'm learning about the class syntax, so I did it that way, but I think the same approach would work with regular objects.
class MyArray {
constructor(num) {
console.log(`building MyArray with ${num} objects`);
this.innerArray = new Array(num);
}
update(num) {
console.log(`updating MyArray to have ${num} objects`);
this.innerArray = new Array(num);
}
get() { return this.innerArray }
}
let foo = new MyArray(10);
foo.get()[0] = "bar";
console.log(foo.get());
foo.update(20);
foo.get()[0] = "baz";
console.log(foo.get());
the way you asked your question is not quite right you should explain better.
not sure this is what you want.
you can't change the this in javascript.
but function in prototype will be called with the object as their context. so Array.prototype.length gives the length in arr.length by having arr as it's context. Array.prototype.splice
so
Array.prototype.testA = function() {
// this.splice(0,this.length);
var a = [3,5,7];
// new Array(23).fill().map( (a,_)=>_)
this.splice(0,this.length,...a);
}
Array.prototype.testAA = function() {
var a = [3,5,7];
this.splice(this.length,0,...a);
}
var a = [1];
a.testAA();
console.log(a);
a.testA();
console.log(a);
With a associative array such as:
var m = {};
m['a'] = {id:1, foo:'bar'};
m['b'] = {id:2, foo:'bar'};
Is it possible to create a prototype such as:
Array.prototype.hello = function() { console.log('hello'); }
m.hello();
This fails because m is an object, so I tired:
Object.prototype.hello = function() { console.log('hello'); }
and this is problematic too.
Is is possible to create a prototype which can operate on this data structure?
Update:
I think I need some sleep :)
When I create and use the Object.prototype.hello = function() { console.log('hello'); } by itself it works fine.
When I add the prototype and include a 3rd party JS Framework, it makes the framework stop working.
Why not create your own object constructor so that you can extend its prototype without issues?
function O(o) {
for (var p in o) {
this[p] = o[p];
}
}
O.prototype.hello = function() { console.log('hello') }
Then use the constructor with your object literals.
var m = new O({})
m['a'] = {id:1, foo:'bar'}
m['b'] = {id:2, foo:'bar'}
There are tricks that'll let you drop the new if you wish.
You can assign custom properties to any object, and that means you can do so on an object with a different immediate underlying prototype than Object.prototype. So you could do this, for instance:
function MyMap() {
}
MyMap.prototype.hello = function() {
console.log('hello');
};
var m = new MyMap();
m['a'] = {id:1, foo:'bar'};
m['b'] = {id:2, foo:'bar'};
m.hello();
Note, though, that if you stored a hello entry:
m['hello'] = {id:3, foo:'bar'};
...it would hide the hello that your object gets from the prototype.
Also note that your m will have the properties not only from MyMap.prototype, but also from Object.prototype (like {} does), like toString and valueOf and hasOwnProperty. If you want to not have Objectproperties, you can do that, too:
function MyMap() {
}
MyMap.prototype = Object.create(null);
MyMap.prototype.hello = function() {
console.log('hello');
};
Also note that constructor functions (MyMap, above) are only one way to create objects with an underlying prototype. You can just use Object.create directly:
var mapPrototype = {
hello: function() {
console.log('hello');
}
};
var m = Object.create(mapPrototype);
You could use Object.create to create an array-like prototype for your structure.
var proto = Object.create(Array.prototype);
proto.hello = function() { console.log('hello'); }
Then use it like
var stack = Object.create(proto);
stack.hello();
stack.push('example');
Assigning to Object.prototype should work just fine. When running in node:
> Object.prototype.foo = function() { console.log("foo!"); }
[Function]
> var m = {};
undefined
> m.foo();
foo!
undefined
Whether it is a good idea is a whole other discussion...
I'm trying to dynamically create function when iterating over an array and
I need the arguments in the array to be set according to the value of the current index.
For example:
var array = ['apple','orange','banana'];
I need to have these three functions:
function() { return 'apple' };
function() { return 'orange' };
function() { return 'banana' };
I tried to return a constructed function from an external one but the expression in it won't evaluate and I end up with three of these:
function() { return array[i] };
Is there a way to dynamically create such a function without using eval()?
You can create the functions like so:
var funcs = {};
for (var i=0;i<array.length;i++)
{
funcs[array[i]] = (function(val)
{
return function()
{
return val;
};
}(array[i]));
}
which can be called like so:
funcs.apple();// returns "apple"
But also, depending on the value of some var:
var someVar = 'banana';
if (funcs.hasOwnProperty(someVar))
{
funcs[someVar]();
}
If what you're after is a single (possibly global) function, that depending on, for example, the URI, you just have to write this:
var myFunc = (function()
{
var retVal = location.mathname.match(/^\/([^\/]+)/)[1];
return function()
{
return retVal;
};
}());
Note that the function won't be hoisted, as it is an expression.
I've written a lot about IIFE's (Immediatly Invoked Function Expressions), how they work and why, so best check my answer here if you don't fully understand these code snippets. It's quite easy once you get the logic, and you'll soon find yourself writing code like this all the time... tey really are incredibly powerful things, closures are!
This is what I would do:
function constant(value) {
return function () {
return value;
};
}
var array = ["apple", "orange", "banana"];
var fruit = array.map(constant);
alert(fruit[0]()); // apple
alert(fruit[1]()); // orange
alert(fruit[2]()); // banana
Simple. See the demo: http://jsfiddle.net/tfS2F/
You can also use the initial array as a key as follows:
alert(fruit[array.indexOf("orange")]()); // orange
See the demo: http://jsfiddle.net/tfS2F/1/
This one will not work, i leave it to illustrate the infamous loop problem:
The best way to achieve this would be to create a context var before creating the function, hope this illustrates it http://jsfiddle.net/jkymq/
var array = ['apple','orange','banana'];
var methods = {}
for (pos = 0 ; pos < array.length; pos ++){
var target = array[pos];
var newMethod = function(){alert (target);}
methods[target] = newMethod;
}
for (foo in methods){
methods[foo]();
}
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.