3

There is so much about creating and inheritance of Array-like objects, but one of my questions remains unanswered: is it possible somehow to add a auto-incrementing .length property to the object?
Sample script:

function ArrayLike(){
    this.length=0;
    this.splice=[].splice;
}
var Arr=new ArrayLike();
Arr[0]=0;
Arr[1]=1;
alert(Arr.length); //alerts "0" instead of "2"

Of course, i can add some method like:

function ArrayLike(){
    this.length=0;
    this.splice=[].splice;
    this.push=function(toadd){
        this[this.length++]=toadd;
    }
}

But this is not what I'm trying to do.

So is it possible to increment .length when I add something to the object in a way Arr[N]='blah-blah-blah'?

11
  • 1
    Why would you want to do this? Just use an Array(), and add extra properties to it, if you with. In JavaScript, Arrays are also objects. They can have properties: var Arr = [0, 1]; arr.myProperty = "foobar"; I'm not saying any of this is a good idea, though. Commented Jan 29, 2015 at 9:10
  • Your best option is probably making length a method, not a property. Commented Jan 29, 2015 at 9:10
  • Please don't downvote this question. It is not written badly and does not fail in any other metric. If he fails to see why this is not a good idea, then the answer should explain why, that's all. Commented Jan 29, 2015 at 9:19
  • 1
    @MadBrozzeR: You really won't gain any performance by copying part of the Array functionality. It's a native object that's well-optimized in modern browsers. Writing your own implementation will negate that optimization, not to mention the loops you'd have to jump through to actually make it work. Commented Jan 29, 2015 at 10:22
  • 1
    @MadBrozzeR: "To win a small bit of performance while iterating..." I can't see any gain by using your own object over a native array, can you explain where you think you'll get that gain? "...or just not to have it overloaded with extra methods." The "extra" methods don't "load" individual array instances in any way at all. Individual array instances have only their own properties, the "extra" methods are on the prototype that sits behind them. There is zero cost associated with them on a per-object basis. Commented Jan 29, 2015 at 10:40

1 Answer 1

3

is it possible somehow to add autoincrement .length property

Not really; at least, you can't do all the things Array does around length and index properties.

In an ES5-enabled environment, you can make length a property with getter and setter functions. But it's awkward to find out what length should be, because there's (so far) no "catch all" way of setting it when code like:

arr[1] = "foo";

...runs. (ES6 may well make it possible to have catch-all property setters, via proxies.)

So your length would have to figure it out from the object contents, which is not going to be very efficient, e.g.:

var explicitLength;
Object.defineProperty(this, "length", {
    get: function() {
        var length;

        // Find our length from our max index property
        length = Object.keys(this).reduce(function(maxIndex, key) {
            var index = key === "" ? NaN : +key;
            if (!isNaN(index) && index > maxIndex) {
                return index;
            }
            return maxIndex;
        }, -1) + 1;

        // If we have an explicitly-set length, and it's greater,
        // use that instead. Note that if explicitLength is
        // `undefined`, the condition will always be false.
        if (explicitLength > length) {
            length = explicitLength;
        }

        return length;
    },
    set: function(value) {
        explicitLength = value;
        // You might choose to have code here removing properties
        // defining indexes >= value, likes Array does
    }
});

Note that even when we have an explicitly-set length, we still have to check to see if the natural length is greater, to deal with:

arr.length = 2;
arr[5] = "foo";
console.log(arr.length); // Should be 6, not 2

Doing that much work on a property retrieval is obviously not a good thing, so I would steer clear of this sort of thing.


Above I said "...so I would steer clear of this sort of thing."

Well that's all very well and good, but what should you do instead?

Suppose you have the functions nifty and spiffy that you want to have available. You have two realistic options:

  1. Use an actual array, and mix in your custom methods to each array instance

  2. Enhance Array.prototype

#1 Use an actual array and mix in your methods

You'd define your methods on a convenient object, say ArrayMixin:

var ArrayMixin = {
    nifty: function() {
        // ...do something nifty with `this`
    },
    spiffy: function() {
        // ...do something spiffy with `this`
    }
};

Then have a builder function for creating arrays:

function createArray(arr) {
    arr = arr || []; // `arr` is optional
    extend(arr, ArrayMixin);
    return arr;
}

What's this extend function, you ask? Something that copies properties from one object to another. If you use any libraries or frameworks (jQuery, Underscore, Angular, PrototypeJS, ...), they're likely to have an extend function of some kind that does this. The usually look something like this (this is very off-the-cuff).

function extend(target) {
    var arg, obj, key;
    for (arg = 1; arg < arguments.length; ++arg) {
        obj = arguments[arg];
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {
                target[key] = obj[key];
            }
        }
    }
    return target;
}

Note that that's a shallow copy, and it doesn't bother to try to make the properties it adds to target non-enumerable. Tweak as desired.

So then when you want an array:

var a = createArray(['one', 'two', 'three']);

...and a has nifty and spiffy on it.

Example:

var ArrayMixin = {
  nifty: function() {
    this.forEach(function(entry, index) {
      this[index] = entry.toUpperCase();
    }, this);
    return this;
  },
  spiffy: function() {
    this.forEach(function(entry, index) {
      this[index] = entry.toLowerCase();
    }, this);
    return this;
  }
};

function createArray(arr) {
  arr = arr || []; // `arr` is optional
  extend(arr, ArrayMixin);
  return arr;
}

function extend(target) {
  var arg, obj, key;
  for (arg = 1; arg < arguments.length; ++arg) {
    obj = arguments[arg];
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        target[key] = obj[key];
      }
    }
  }
  return target;
}

var a = createArray(['one', 'two', 'three']);
a.nifty();
snippet.log(a.join(", "));
a.spiffy();
snippet.log(a.join(", "));
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Enhance Array.prototype

People freak out about enhancing Array.prototype, but there's really nothing wrong with it. The only reason it causes trouble is if people use broken code that expects for-in to loop through array indexes and you've had to enhance the prototype without making the new methods non-enumerable. But again, that code is broken; for-in isn't for looping through array indexes, it's for looping through object properties.

So here's enhancing Array.prototype:

(function(aproto) {
    var add;
    if (Object.defineProperty) {
        // Function to add a non-enumerable property
        add = function(obj, prop, value) {
            Object.definePrperty(obj, prop, {
                value: value
            });
        };
    } else {
        // Old JavaScript engine, oh well
        add = function(obj, prop, value) {
            obj[prop] = value;
        };
    }

    add(aproto, "nifty", function() {
        // ...do something nifty with `this`
    });

    add(aproto, "spiffy", function() {
        // ...do something spiffy with `this`
    });

})(Array.prototype);

Now, using it is really clean:

var a = ['one', 'two', 'three'];

...and a has nifty and spiffy on it.

Example:

(function(aproto) {
  var add;
  if (Object.defineProperty) {
    // Function to add a non-enumerable property
    add = function(obj, prop, value) {
      Object.defineProperty(obj, prop, {
        value: value
      });
    };
  } else {
    // Old JavaScript engine, oh well
    add = function(obj, prop, value) {
      obj[prop] = value;
    };
  }

  add(aproto, "nifty", function() {
    this.forEach(function(entry, index) {
      this[index] = entry.toUpperCase();
    }, this);
  });

  add(aproto, "spiffy", function() {
    this.forEach(function(entry, index) {
      this[index] = entry.toLowerCase();
    }, this);
  });

})(Array.prototype);


var a = ['one', 'two', 'three'];
a.nifty();
snippet.log(a.join(", "));
a.spiffy();
snippet.log(a.join(", "));
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Sign up to request clarification or add additional context in comments.

6 Comments

^ This is why it's basically a good idea to work with a Array() as your "source". Extend that (if you have to), instead of trying to copy functions.
Well, this is totally not a way to simplicity, but anyway, the answer satisfies the question.
@MadBrozzeR: Re simplicity: Exactly. :-) Much better off just using an array.
@T.J.Crowder Like I said before, the way I do it right now (in my js library, not here) is very similar to "Use an actual array, and mix in your custom methods to each array instance", but without an extra object: var arr=NLtoARR(NodeList); arr.Nprops=1; arr.Nmeths=function(){return}; return arr; I came here to ask for better solution. So, I guess, I prefer this one now. Thank you. The answer is accepted.
Just checked for performance between new Array() and new ArrayLike() (custom object). The first one is much faster.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.