JavaScript:在数组上使用defineProperty访问器's长度

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

本文关键字:长度 访问 defineProperty 数组 JavaScript      更新时间:2023-09-26

我希望(主要是出于学术原因)能够使用Object.defineProperty()在数组的length上设置访问器,以便通知大小更改。

我知道ES6对象observe和watch.js,但如果可能的话,我想尝试在没有额外库的情况下在ES5中实现这一点,即使这只适用于V8/Chrome。

示例阵列:

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

遗憾的是,Chrome,开箱即用,使长度不可配置:

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

它不起作用:

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

'TypeError: Cannot redefine property: length' 故障

正如您所看到的,configurable就是false,所以失败是可以理解的。然而,根据MDN,这应该是可能的。

如何让defineProperty处理数组的length属性?这样行吗?

根据ECMAScript 15.4.5.1,数组有自己的[[DefineOwnProperty]]内部方法,因此configurable: false不一定会立即破坏交易。这种方法的早期步骤是:

3.如果p是"length",则

a.如果Desc的[[Value]]字段不存在,则

i.返回调用A的默认[[DefineOwnProperty]]内部方法(8.12.9)的结果,并将"length"、Desc和Throw作为参数。

因此,如果属性描述符中没有value属性,则属性设置操作将委托给默认的[[DefineOwnProperty]]方法。ECMAScript 15.4.5.2要求length属性具有configurable: false,因此默认方法将失败。

如果设置value,为了避免陷入默认方法,您也不能定义setter。尝试这样做将导致Chrome(或任何符合第8.10节的浏览器)出现错误:

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

因此,在任何符合ES5的实现中,似乎都不可能在数组length上定义setter。

请注意,MDN的文章似乎是在谈论浏览器错误地拒绝使用defineProperty设置value通常应该,但有时由于错误而不可能。

"向Array.length添加事件侦听器将对应用程序的整体性能产生巨大影响,您应该尽一切努力避免"@Juno;

"不要依赖于重新定义数组的长度属性来工作,或者以特定的方式工作"MDN;

由于我们不能触摸长度,为了有类似的行为,我们可以更改push方法,并使用它向数组添加新值。

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

由于阅读了更多关于这方面的内容,Kangax关于数组子类化主题的优秀文章涵盖了各种技术。一种被称为Array原型注入的技术用于在Ractive.js等流行库中对Array进行子类化。它依赖于非规范,但流行的__proto__被公开,但允许长度上的"访问器"。

rmprop.js让您代理一个数组,这样您就可以使用.length属性执行任何您想要的操作:

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

正如文档中明确指出的:

仅显示Internet Explorer 9及更高版本,以及Firefox 23及更新版本以完全正确地实现长度属性的重新定义阵列。目前,不要依赖于重新定义一个数组,用于工作或以特定方式工作。甚至当你可以依赖它时,真的没有充分的理由这么做。

并非所有浏览器(包括Chrome)都支持它,您应该首先找到另一种方法来执行您想要执行的操作,而不必更改Array的length属性。

一旦用configurable: false定义了属性,就无法更改其配置。

所以在这种情况下,这是不可能的。即使是这样,它也会有性能问题,因为Array.length被所有库到处使用,访问频率很高,而且到处都在不断变化。

Array.length中添加事件侦听器将对应用程序的整体性能产生巨大影响,您应该尽量避免这种情况。

检查"代理";对象:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

无需库