2014-02-24 35 views
13

是否有可能在Javascript中創建一個長度保持不變的數組?是否有可能在JavaScript中創建一個固定長度的數組?

例如,數組A創建的長度爲2.隨後,任何嘗試調用A.push()或A.pop()或設置A [5]的值都將失敗。 A.length將始終爲2.

這是鍵入數組(例如Float32Array)已經工作的方式。他們有固定的大小。但是我想要一種在常規數組上獲得相同行爲的方法。

對於我的具體情況,我想創建一個固定長度的數組,其中每個條目是一個對象。但我仍然想知道一般問題的答案。

+2

與天然陣列,沒辦法。但是你可以創建一個類似數組的對象。 –

+3

你可以自己實現它。只需包裝一個數組對象,但不要暴露「push」,「pop」或其他修改方法。如果你想保持內部包裝數組不可用,你可以使用閉包 – slezica

+1

爲什麼它必須是一個數組?只需使用一個'object'然後['freeze'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze),這樣屬性就不能改性。請注意,如果您有對象的對象,則凍結僅適用於您凍結的對象,而不適用於它的任何嵌套對象。 – Andy

回答

4

更新:

Object.seal(這是ES2015的一部分)將做到這即:

var a = new Array(42); 
if(Object.seal) { 
    Object.seal(a); 
    // now a is a fixed-size array with mutable entries 
} 

原來的答案:

差不多。然而,你不能改變一個冷凍的對象的值

var a = new Array(2); 

// set values, e.g. 
a[0] = { b: 0; } 
a[1] = 0; 

Object.freeze(a); 

a.push(); // error 
a.pop(); // error 
a[1] = 42; // will be ignored 
a[0].b = 42; // still works 

:由於被titusfx建議您可以凍結的對象。 如果您有一組對象,這可能不會成爲問題,因爲您仍然可以使用 更改對象的值。

對於數組數組,當然有typed arrays

Object.freezeES2015 but most browsers seem to support it, including IE9的一部分。你當然功能測試的:

if(Object.freeze) { Object.freeze(obj); }

5

更新:

接受的答案顯示了這一問題可以現在可以使用Object.seal這是不可用的時候解決。

原來的答案:

因此,它似乎是答案,原來的問題僅僅是「否」。不可能創建固定長度的本地javascript數組。

但是,您可以創建一個對象,其行爲類似於固定長度的數組。根據評論中的建議,我已經提出了兩種可能的實現方式,既有優點也有缺點。

我還沒有想出我將在我的項目中使用2箇中的哪一個。我也不是100%滿意。請讓我知道你是否有任何改進它們的想法(我熱衷於使這些對象儘可能快速和高效,因爲我需要很多它們)。

下面兩個實現的代碼以及說明使用情況的QUnit測試。

// Version 1 
var FixedLengthArrayV1 = function(size) { 
    // create real array to store values, hidden from outside by closure 
    var arr = new Array(size); 
    // for each array entry, create a getter and setter method 
    for (var i=0; i<size; i++) {FixedLengthArrayV1.injectArrayGetterSetter(this,arr,i);} 
    // define the length property - can't be changed 
    Object.defineProperty(this,'length',{enumerable:false,configurable:false,value:size,writable:false}); 
    // Could seal it at this point to stop any other properties being added... but I think there's no need - 'length' won't change, so loops won't change 
    // Object.seal(this); 
}; 
// Helper function for defining getter and setter for the array elements 
FixedLengthArrayV1.injectArrayGetterSetter = function(obj,arr,i) { 
    Object.defineProperty(obj,i,{enumerable:true,configurable:false,get:function(){return arr[i];},set:function(val){arr[i]=val;}}); 
}; 
// Pros: Can use square bracket syntax for accessing array members, just like a regular array, Can loop just like a regular array 
// Cons: Each entry in each FixedLengthArrayV1 has it's own unique getter and setter function - so I'm worried this isn't very scalable - 100 arrays of length 100 means 20,000 accessor functions in memory 


// Version 2 
var FixedLengthArrayV2 = function(size) { 
    // create real array to store values, hidden from outside by closure 
    var arr = new Array(size); 
    this.get = function(i) {return arr[i];} 
    this.set = function(i,val) { 
     i = parseInt(i,10); 
     if (i>=0 && i<size) {arr[i]=val;} 
     return this; 
    } 
    // Convenient function for looping over the values 
    this.each = function(callback) { 
     for (var i=0; i<this.length; i++) {callback(arr[i],i);} 
    }; 
    // define the length property - can't be changed 
    Object.defineProperty(this,'length',{enumerable:false,configurable:false,value:size,writable:false}); 
}; 
// Pros: each array has a single get and set function to handle getting and setting at any array index - so much fewer functions in memory than V1 
// Cons: Can't use square bracket syntax. Need to type out get(i) and set(i,val) every time you access any array member - much clumsier syntax, Can't do a normal array loop (need to rely on each() helper function) 



// QUnit tests illustrating usage 
jQuery(function($){ 

    test("FixedLengthArray Version 1",function(){ 

     // create a FixedLengthArrayV2 and set some values 
     var a = new FixedLengthArrayV1(2); 
     a[0] = 'first'; 
     a[1] = 'second'; 

     // Helper function to loop through values and put them into a single string 
     var arrayContents = function(arr) { 
      var out = ''; 
      // Can loop through values just like a regular array 
      for (var i=0; i<arr.length; i++) {out += (i==0?'':',')+arr[i];} 
      return out; 
     }; 

     equal(a.length,2); 
     equal(a[0],'first'); 
     equal(a[1],'second'); 
     equal(a[2],null); 
     equal(arrayContents(a),'first,second'); 

     // Can set a property called '2' but it doesn't affect length, and won't be looped over 
     a[2] = 'third'; 
     equal(a.length,2); 
     equal(a[2],'third'); 
     equal(arrayContents(a),'first,second'); 

     // Can't delete an array entry 
     delete a[1]; 
     equal(a.length,2); 
     equal(arrayContents(a),'first,second'); 

     // Can't change the length value 
     a.length = 1; 
     equal(a.length,2); 
     equal(arrayContents(a),'first,second'); 

     // No native array methods like push are exposed which could let the array change size 
     var errorMessage; 
     try {a.push('third');} catch (e) {errorMessage = e.message;} 
     equal(errorMessage,"Object [object Object] has no method 'push'"); 
     equal(a.length,2); 
     equal(arrayContents(a),'first,second');  

    }); 

    test("FixedLengthArray Version 2",function(){ 


     // create a FixedLengthArrayV1 and set some values 
     var a = new FixedLengthArrayV2(2); 
     a.set(0,'first'); 
     a.set(1,'second'); 

     // Helper function to loop through values and put them into a single string 
     var arrayContents = function(arr) { 
      var out = ''; 
      // Can't use a normal array loop, need to use 'each' function instead 
      arr.each(function(val,i){out += (i==0?'':',')+val;}); 
      return out; 
     }; 

     equal(a.length,2); 
     equal(a.get(0),'first'); 
     equal(a.get(1),'second'); 
     equal(a.get(2),null); 
     equal(arrayContents(a),'first,second'); 

     // Can't set array value at index 2 
     a.set(2,'third'); 
     equal(a.length,2); 
     equal(a.get(2),null); 
     equal(arrayContents(a),'first,second'); 

     // Can't change the length value 
     a.length = 1; 
     equal(a.length,2); 
     equal(arrayContents(a),'first,second'); 

     // No native array methods like push are exposed which could let the array change size  
     var errorMessage; 
     try {a.push('third');} catch (e) {errorMessage = e.message;} 
     equal(errorMessage,"Object [object Object] has no method 'push'"); 
     equal(a.length,2); 
     equal(arrayContents(a),'first,second');  

    }); 


}); 
+3

我希望您的結論很清楚:不要這樣做。 –

1

當前答案是YES,您可以。有幾種方法可以做到這一點,但一些網絡瀏覽器有它自己的「解釋」。

  1. 解決方案與Firefox Mozzila控制檯測試:

var x = new Array(10).fill(0); 
 
// Output: undefined 
 
Object.freeze(x); 
 
// Output: Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] 
 
x.push(11) 
 
// Output: TypeError: can't define array index property past the end of an array with non-writable length 
 
x.pop() 
 
// Output: TypeError: property 9 is non-configurable and can't be deleted [Learn More] 
 
x[0]=10 
 
// Output: 10 // You don't throw an error but you don't modify the array 
 
x 
 
// Output: Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]

重要的是要注意的是,如果數組是對象,你需要做的,冰櫃代替。深凍的代碼是here

  • 一個包裝了數組(這是更好,如果你不想拋出異常)

  • 隨着ES2015代碼應工作的後續解決方案A級但事實並非如此:

  • var x = new Array(10).fill(0); 
     
    Object.freeze(x.length); 
     
    x.push(3); 
     
    console.log(x);
    選中此 page in the section Note

    +0

    問題1 - 您無法寫入值。 3看起來不錯 - 也許它會有一天工作。 –

    +0

    你的第三個選擇是錯誤的。 'x.length'是一個數字,'Object.freeze(x.length); '只會返回'x.length'。 –

    +0

    @ tim-我知道,我寫到了,請閱讀答案和評論。我說它應該有一天工作,但事實並非如此。這是因爲如果價值變化發生錯誤,凍結應該是一個觀察者。這是問題的最簡單的解決方案,但事實並非如此。而且應該有一天我會開一個問題並被接受。所以,有一天它會起作用。 – titusfx

    -1

    我知道這是一個老問題,但現在有,不只是這個被稱爲節點模塊fixed-array

    +1

    該節點模塊並沒有真正的幫助。如果給你一個表示固定數組的對象,但是你不能通過索引來訪問這些值(你必須以數組的形式獲取這些值的副本,但副本不是固定長度的),並且你不能寫一個特定點的價值,只能推到最後。如前面的答案中所建議的,包裝數組的對象更加靈活。 –

    0

    其實創建一個完全優化的真正的C JS中像固定陣列上最現代的瀏覽器(包括IE 11),你可以使用:TypedArray或ArrayBuffer像這樣:

    var int16 = new Int16Array(1); // or Float32Array(2) 
    int16[0] = 42; 
    console.log(int16[0]); // 42 
    int16[1] = 44; 
    console.log(int16[1]); // undefined 
    

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray

    +0

    如果他們需要一個數組,其中條目都具有相同的數據類型,這可能對人有所幫助。原來的問題已經提到Float32Array作爲一個例子。當數組的條目可能不是同一類型時,我認爲它不會有幫助。 –

    相關問題