Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: use defineProperty accessor on an array's .length?

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?

like image 698
mikemaccana Avatar asked Sep 05 '13 14:09

mikemaccana


People also ask

Can you change the length of an array in JavaScript?

You can set the value of the length property to change the size of an array. If you set length to be smaller than its previous value, the array is truncated, and elements at the end are lost.

How do you find the length of an array in JavaScript?

The JavaScript array length property states the number of items in an array. To find the length of an array, reference the object array_name. length. The length property returns an integer.

What parameters does the function object defineProperty () accept?

Parameters. The object on which to define the property. The name or Symbol of the property to be defined or modified. The descriptor for the property being defined or modified.


2 Answers

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.

like image 156
apsillers Avatar answered Oct 04 '22 22:10

apsillers


"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');
like image 40
rafaelcastrocouto Avatar answered Oct 04 '22 22:10

rafaelcastrocouto