Question

I would like (mainly for academic reasons) to be able to set an accessor on an array's length using Object.defineProperty(), so than I can notify for size changes.

I am aware of ES6 object observe, and watch.js, but I would like to attempt to do this in ES5 without extra libraries if possible, even if this is only for V8/Chrome.

A sample array:

var demoArray = ['one', 'two']

Alas Chrome, out of the box, makes length not configurable:

Object.getOwnPropertyDescriptor(demoArray, 'length')
Object {value: 2, writable: true, enumerable: false, configurable: false}

And it doesn't work:

Object.defineProperty(demoArray, 'length', { set: function(){ console.log('length changed!')} })

Fails with 'TypeError: Cannot redefine property: length'

As you can see, configurable is false - so the failure is understandable. However according to MDN it should be possible.

How can I get defineProperty working on an array's length property? Should this work?

Était-ce utile?

La solution 3

Since reading a little more about this, Kangax's excellent article dedicated to the topic of subclassing Array covers a a variety of techniques. One technique, called Array prototype injection is used to subclass Array in popular libraries like Ractive.js. It relies on the non-spec, but popular __proto__ being exposed, but does allow 'accessors' on length.

Autres conseils

According to ECMAScript 15.4.5.1, arrays have their own [[DefineOwnProperty]] internal method, so the configurable: false is not necessarily an immediate deal-breaker. An early step in this method says:

3. If P is "length", then

  a. If the [[Value]] field of Desc is absent, then

     i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", Desc, and Throw as arguments.

Therefore, if you don't have a value attribute in your property descriptor, the property setting operation is delegated to the default [[DefineOwnProperty]] method. ECMAScript 15.4.5.2 requires that the length property have configurable: false, so the default method will fail.

If you do set value, to avoid dropping into the default method, you cannot also define a setter. Attempting to do so will cause an error in Chrome (or any browser in compliance with section 8.10):

TypeError: Invalid property. A property cannot both have accessors and be writable or have a value

Therefore, it seems impossible to define a setter on array length in any ES5-compliant implementation.

Note that the MDN article seems to be talking about browsers erroneously refusing to set the value using defineProperty, which should be generally possible, but sometimes is not, due to a bug.

"Adding a event listener to Array.length will cause a huge impact on overall performance of your application, and you should avoid it by all means" @Juno;

"don't rely on redefining the length property of an array to either work, or to work in a particular manner" MDN;

Since we can't touch length, to have a similar behavior we can change the push method and aways use it to add new values to the array.

var a = ['a','b'];
a.push = function(x){
  console.log('added: ',x,', length changed: ',(this.length+1)); 
  this[this.length]=x
}
a.push('c');

rmprop.js let's you proxy an array so that you can do whatever you want with the .length property:

var rmprop = require('rmprop');
var arr = rmprop(['my','array']);

// instead, length will return sum of lengths of all elements
Object.defineProperty(arr, 'length', {
    get: function() {
        return this[unprop.real].join('').length;
    },
};

arr.length; // 7

As it clearly say on the document:

Only Internet Explorer 9 and later, and Firefox 23 and later, appear to fully and correctly implement redefinition of the length property of arrays. For now, don't rely on redefining the length property of an array to either work, or to work in a particular manner. And even when you can rely on it, there's really no good reason to do so.

It is not supported by all browsers including Chrome, and you should find another way to do what you desire to do in the first place without changing length property of Array.

Once, a property is defined with configurable: false, there is really no way to change its configurations.

So in this case, it in not possible. And even if it is, it will have performance issues because Array.length is used everywhere by all libraries, is accessed so often, and is constantly changing everywhere.

Adding a event listener to Array.length will cause a huge impact on overall performance of your application, and you should avoid it by all means.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top