4

我想(主要是出於學術的原因)能夠使用Object.defineProperty()來設置陣列的length上的存取器,所以比我可以通知大小更改。JavaScript:在數組的.length中使用defineProperty訪問器?

我知道ES6對象觀察和watch.js,但我想嘗試在沒有額外的庫的情況下在ES5中做到這一點,如果可能的話,即使這隻適用於V8/Chrome。

甲樣本陣列:

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

唉鉻,開箱,使得長度不可配置:

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'

正如你所見,configurablefalse - 所以失敗是可以理解的。但according to MDN it should be possible

我如何獲得defineProperty處理陣列的length屬性?這應該工作嗎?

+0

我認爲MDN頁面(並從那裏的鏈接),僅涉及到重新定義'writable'財產描述符(也可能是'value'),但將數據屬性更改爲訪問器屬性是絕對禁止的。 – Bergi

+1

我做了一個編輯(在你接受之後)可能會清除MDN段落:它似乎在討論一個bug,它使用'defineProperty'來阻止設置'value',根據規範它應該*是可能的,但是有些實現有時會被錯誤地阻塞(就好像它錯誤地使用'writable:false')。然而,定義一個setter是正確的,符合規範。 – apsillers

+0

是啊http://whereswalden.com/2013/08/05/new-in-firefox-23-the-length-property-of-an-array-can-be-made-non-writable-but-you- shouldnt-do-it/comment-page-1 /#comment-162704談論同樣的事情。設置一個* data *屬性工作正常:'Object.defineProperty(demoArray,'length',{value:0});'但是設置一個訪問器屬性將不起作用並且破壞規範。 – mikemaccana

回答

1

ECMAScript 15.4.5.1,陣列有自己的[[DefineOwnProperty]]內部方法,所以configurable: false不一定立即致命弱點。在這種方法的早期步驟說:

3.如果P爲"length",然後

 一個。如果Desc的[[Value]]字段不存在,則

          i。返回調用默認[[DefineOwnProperty]]內部方法(8.12.9)的結果,傳遞"length",Desc和Throw作爲參數。

因此,如果你沒有在你的財產描述一個value屬性,該屬性設置操作委託給默認[[DefineOwnProperty]]方法。 ECMAScript 15.4.5.2要求length屬性有configurable: false,所以默認方法將失敗。

如果你設置value,爲了避免掉入默認方法,你不能同時定義一個setter。試圖這樣做會導致Chrome的錯誤(或符合任何瀏覽器section 8.10):

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

因此,這似乎是不可能的兼容ES5的任何定義的數組length二傳手實現。

注意,MDN文章似乎是在談論瀏覽器錯誤地拒絕設定使用valuedefineProperty,這應該是普遍可行的,但有時不是,因爲一個錯誤。

+0

感謝您的全面回答。將這個基本歸結爲.length既是數據描述符又是訪問符描述符是否是正確的?我讀過一些類似的東西:http://whereswalden.com/2013/08/05/new-in-firefox-23-the-length-property-of-an-array-can-be-made-non可以寫,但你不應該做它/ – mikemaccana

+1

@nailer大多數情況下它只是歸結爲'可配置:false'。數組的自定義'[[DefineOwnProperty]]'允許具有'value'的數據描述符。當你試圖定義一個* accessor *描述符(當然,沒有'value')時,自定義方法會拋出缺省方法,這會立即阻止你,因爲你試圖違反'configurable:false'。自定義方法不強制執行「可配置:false」要求,但自定義方法僅允許使用「值」項設置數據描述符。 – apsillers

0

因爲它在文件中明確說:

只有Internet Explorer 9中和後,和Firefox 23及更高版本,出現 全面正確地貫徹數組的長度財產 的重新定義。目前,不要依靠重新定義數組的長度屬性來使其工作,或以特定方式工作。當你可以依靠它時,甚至沒有充分的理由這樣做。

它不是由所有的瀏覽器包括Chrome的支持,你應該找到另一種方式做你想要在第一時間做不改變陣列的length財產。

有一次,一個屬性被定義爲configurable: false,真的沒有辦法改變它的配置。

所以在這種情況下,它是不可能的。即使是這樣,它也會有性能問題,因爲所有圖書館都在使用Array.length,所以經常訪問,並且在任何地方都在不斷變化。

將事件監聽器添加到Array.length將會對應用程序的整體性能產生巨大影響,您應該儘量避免它。

+0

啊,我錯過了我提到的文檔中的'唯一'部分。如前所述,雖然我並不打算監控從Array繼承的所有對象,只是一個數組。 – mikemaccana

1

「將事件監聽器添加到Array.length將會對應用程序的整體性能產生巨大影響,您應該儘量避免使用它」@Juno;

「不要依賴重新定義數組的長度屬性來工作,或以特定方式工作」MDN;

由於我們無法觸及長度,因此我們可以更改推送方法並使用它向數組中添加新值。

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

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