47

i try to extend Array object in javascript with some user friendly methods like Array.Add() instead Array.push() etc...

i implement 3 ways to do this. unfortunetly the 3rd way is not working and i want to ask why? and how to do it work.

//------------- 1st way
Array.prototype.Add=function(element){
     this.push(element);
};

var list1 = new Array();
list1.Add("Hello world");
alert(list1[0]);

//------------- 2nd way
function Array2 () {
    //some other properties and methods
};

Array2.prototype = new Array;
Array2.prototype.Add = function(element){
  this.push(element);  
};

var list2 = new Array2;
list2.Add(123);
alert(list2[0]);

//------------- 3rd way
function Array3 () {
    this.prototype = new Array;
    this.Add = function(element){
      this.push(element);  
    };
};

var list3 = new Array3;
list3.Add(456);  //push is not a function
alert(list3[0]); // undefined

in 3rd way i want to extend the Array object internally Array3 class. How to do this so not to get "push is not a function" and "undefined"?

Here i add a 4th way.

//------------- 4th way
function Array4 () {
    //some other properties and methods
    this.Add = function(element){
        this.push(element);
    };
 };
Array4.prototype = new Array();

var list4 = new Array4();
list4.Add(789);
alert(list4[0]);

Here again i have to use prototype. I hoped to avoid to use extra lines outside class constructor as Array4.prototype. I wanted to have a compact defined class with all pieces in one place. But i think i cant do it otherwise.

demosthenes
  • 1,151
  • 1
  • 13
  • 17
  • If you add a method to Array you will break foreach() on arrays – SpacedMonkey Jul 05 '12 at 09:08
  • Have you looked into coffee script? I will update my answer with an example – SMathew Jul 05 '12 at 21:23
  • i will not add a method to Array.prototype as in 1st example. This was a test. I will create a class which will extend Array object. For example jsArray. The jsArray objects will be Arrays but with more features. – demosthenes Jul 06 '12 at 09:23
  • i saw today coffee script. i did not like its syntax. – demosthenes Jul 06 '12 at 09:29
  • @SpacedMonkey if someone use my custom js lib can adjust his foreach() not to include the last 2 elements of enumeration cause it is the type of object and its length. – demosthenes Jul 06 '12 at 10:16
  • In all your examples you extend Array into a new type (e.g. Array3). Are there any reasons not to simply extend Array, like the OP's first example? `Array.prototype.add = Array.prototype.push;` – trusktr Mar 26 '14 at 22:34

8 Answers8

39

ES6

class SubArray extends Array {
    last() {
        return this[this.length - 1];
    }
}
var sub = new SubArray(1, 2, 3);
sub // [1, 2, 3]
sub instanceof SubArray; // true
sub instanceof Array; // true

Using __proto__

(old answer, not recommended, may cause performance issues)

function SubArray() {
  var arr = [ ];
  arr.push.apply(arr, arguments);
  arr.__proto__ = SubArray.prototype;
  return arr;
}
SubArray.prototype = new Array;

Now you can add your methods to SubArray

SubArray.prototype.last = function() {
  return this[this.length - 1];
};

Initialize like normal Arrays

var sub = new SubArray(1, 2, 3);

Behaves like normal Arrays

sub instanceof SubArray; // true
sub instanceof Array; // true
Community
  • 1
  • 1
laggingreflex
  • 32,948
  • 35
  • 141
  • 196
  • 1
    While using `__proto__` is supported in most JavaScript implementations, it wasn't a standard feature until being marked as a legacy feature in es6. Also it can cause performance issues if used heavily. Using it is generally a bad idea. – Insomniac Feb 13 '16 at 18:09
  • @TBotV63 agreed. in es6 you can simply extend an Array class. updated my answer – laggingreflex Feb 14 '16 at 01:45
  • 2
    You should pass the values in your `constructor` to `super`, like this: `constructor(...items) {super(...items) }` – Kevin Beal Oct 14 '16 at 22:33
  • added an extra property in the constructor this.weight, now it is iterable using for-in, how to make it non-iterable? – shivshankar Aug 07 '19 at 11:08
  • @shivshankar Are you sure you wanna use `for-in` and not `for-of`? Former would treat it as an object, latter as an array(/iterable). – laggingreflex Aug 07 '19 at 12:34
  • That is not the concern, I just don't want to iterate new prop anyway. – shivshankar Aug 07 '19 at 13:03
  • To add a non-iterable property, use: `Object.defineProperty(this, 'weight', {writable:true, value:1234})` instead of `this.weight = 1234`, when setting it for the first time (i.e. in the constructor) – 12Me21 Jun 04 '23 at 16:07
32

Method names should be lowercase. Prototype should not be modified in the constructor.

function Array3() { };
Array3.prototype = new Array;
Array3.prototype.add = Array3.prototype.push

in CoffeeScript

class Array3 extends Array
   add: (item)->
     @push(item) 

If you don't like that syntax, and you HAVE to extend it from within the constructor, Your only option is:

// define this once somewhere
// you can also change this to accept multiple arguments 
function extend(x, y){
    for(var key in y) {
        if (y.hasOwnProperty(key)) {
            x[key] = y[key];
        }
    }
    return x;
}


function Array3() { 
   extend(this, Array.prototype);
   extend(this, {
      Add: function(item) {
        return this.push(item)
      }

   });
};

You could also do this

ArrayExtenstions = {
   Add: function() {

   }
}
extend(ArrayExtenstions, Array.prototype);



function Array3() { }
Array3.prototype = ArrayExtenstions;

In olden days, 'prototype.js' used to have a Class.create method. You could wrap all this is a method like that

var Array3 = Class.create(Array, {
    construct: function() {

    },    
    Add: function() {

    }
});

For more info on this and how to implement, look in the prototype.js source code

SMathew
  • 3,993
  • 1
  • 18
  • 10
  • yes this is the 2nd way. I use in purpose Add instead add because i intend to write some extend methods like visual basic look and feel. – demosthenes Jul 05 '12 at 05:09
  • ok i cant use prototype inside constructor. Is there any way to extend Array object inside constructor? – demosthenes Jul 05 '12 at 05:31
  • 1
    Yes, but it's expensive if you have to instantiate a lot of objects. See http://api.jquery.com/jQuery.extend/ for an example Implementation. Using that technique you can. function Array3() { extend(this, { add: function(item) { return this.push(item) } }) }; – SMathew Jul 05 '12 at 06:17
  • 1
    Oh, you can also use 'Object.defineProperty' to set up getters and setters. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty – SMathew Jul 05 '12 at 06:26
  • its syntax is something new for me. i feel javascript more familiar – demosthenes Jul 06 '12 at 09:31
  • In the first example, why `Array3.prototype = new Array;` and not `new Array();`? (with and without parentheses). Thanks. – pilau May 24 '14 at 18:21
4

A while ago I read the book Javascript Ninja written by John Resig, the creator of jQuery. He proposed a way to mimic array-like methods with a plain JS object. Basically, only length is required.

var obj = {
    length: 0, //only length is required to mimic an Array
    add: function(elem){
        Array.prototype.push.call(this, elem);
    },
    filter: function(callback) {
        return Array.prototype.filter.call(this, callback); //or provide your own implemetation
    }
};

obj.add('a');
obj.add('b');
console.log(obj.length); //2
console.log(obj[0], obj[1]); //'a', 'b'

I don't mean it's good or bad. It's an original way of doing Array operations. The benefit is that you do not extend the Array prototype. Keep in mind that obj is a plain object, it's not an Array. Therefore obj instanceof Array will return false. Think obj as a façade.

If that code is of interest to you, read the excerpt Listing 4.10 Simulating array-like methods.

roland
  • 7,695
  • 6
  • 46
  • 61
2

In your third example you're just creating a new property named prototype for the object Array3. When you do new Array3 which should be new Array3(), you're instantiating that object into variable list3. Therefore, the Add method won't work because this, which is the object in question, doesn't have a valid method push. Hope you understand.

Edit: Check out Understanding JavaScript Context to learn more about this.

Wilfred Hughes
  • 29,846
  • 15
  • 139
  • 192
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • i see, this.prototype is treated as property of Array3, thanks – demosthenes Jul 05 '12 at 04:59
  • 1
    Also don't forget to add `()` when you instantiate objects. `Array` is also an object so `new Array()` (or tipically just `[]`). It works because browsers are being smart. – elclanrs Jul 05 '12 at 05:03
2

You can also use this way in ES6:

Object.assign(Array.prototype, {
    unique() {
      return this.filter((value, index, array) => {
        return array.indexOf(value) === index;
      });
    }
});

Result:

let x = [0,1,2,3,2,3];
let y = x.unique();
console.log(y); // => [0,1,2,3]
selahattinunlu
  • 312
  • 2
  • 8
0

Are you trying to do something more complicated then just add an alias for "push" called "Add"?

If not, it would probably be best to avoid doing this. The reason I suggest this is a bad idea is that because Array is a builtin javascript type, modifying it will cause all scripts Array type to have your new "Add" method. The potential for name clashes with another third party are high and could cause the third party script to lose its method in favour of your one.

My general rule is to make a helper function to work on the Array's if it doesnt exist somewhere already and only extend Array if its extremely necessary.

duyker
  • 800
  • 9
  • 17
  • for now i just learn some new things about js. I want to write some extend methods for js to look like Visual Basic core functions. This is more suitable and easier for me in writing web projects. – demosthenes Jul 05 '12 at 05:05
  • Sounds like a great exercise! Just what you need learn more JS. – elclanrs Jul 05 '12 at 05:08
  • awesome, good luck with it. I just didn't want you running into the common mistake people make of extending a builtin type and clobbering a third party extension and spending hours trying to figure out why a library isn't working (which i have done before :S ) – duyker Jul 05 '12 at 09:20
  • no i wont use any other third party API. It will be independent. – demosthenes Jul 05 '12 at 11:32
0

You CANNOT extend the Array Object in JavaScript.

Instead, what you can do is define an object that will contain a list of functions that perform on the Array, and inject these functions into that Array instance and return this new Array instance. What you shouldn't do is changing the Array.prototype to include your custom functions upon the list.

Example:

function MyArray() {
  var tmp_array = Object.create(Array.prototype);
  tmp_array = (Array.apply(tmp_array, arguments) || tmp_array);
  //Now extend tmp_array
  for( var meth in MyArray.prototype )
    if(MyArray.prototype.hasOwnProperty(meth))
      tmp_array[meth] = MyArray.prototype[meth];
  return (tmp_array);
}
//Now define the prototype chain.
MyArray.prototype = {
  customFunction: function() { return "blah blah"; },
  customMetaData: "Blah Blah",
}

Just a sample code, you can modify it and use however you want. But the underlying concept I recommend you to follow remains the same.

Boopathi Rajaa
  • 4,659
  • 2
  • 31
  • 53
  • 1
    I find it is bad practice to assign `prototype`. It is always better to assign properties of the existing `prototype` object. – jchook Jun 09 '14 at 01:51
  • 1
    You say that you cannot extend the Array Object but acknowledge that adding functions to Array.prototype is possible... your answer seems contradictory to me – Purefan Feb 19 '15 at 09:26
  • 1
    @Purefan - This may be contradictory, but it does help in understanding what Javascript does (and doesn't) do - which is counter-intuitive to people from outside the JS world. – Katinka Hesselink Sep 19 '17 at 10:12
0
var SubArray = function() {                                           
    var arrInst = new Array(...arguments); // spread arguments object
    /* Object.getPrototypeOf(arrInst) === Array.prototype */
    Object.setPrototypeOf(arrInst, SubArray.prototype);     //redirectionA
    return arrInst; // now instanceof SubArray
};

SubArray.prototype = {
    // SubArray.prototype.constructor = SubArray;
    constructor: SubArray,

    // methods avilable for all instances of SubArray
    add: function(element){return this.push(element);},
    ...
};

Object.setPrototypeOf(SubArray.prototype, Array.prototype); //redirectionB

var subArr = new SubArray(1, 2);
subArr.add(3); subArr[2]; // 3

The answer is a compact workaround which works as intended in all supporting browsers.

Neni
  • 131
  • 3