枕を欹てて聴く

香炉峰の雪は簾を撥げて看る

固定長配列

id:uupaa さんが固定長配列があるといいなあと思う時があるという話を受けて. 食いついてしまいましたー...

固定長配列

ECMA262 5.1thにおいては固定長配列を利用することができます. Arrayのlengthの[[Writable]]をfalseにしてしまえばいいですー!
仕様ではこの機能を説明するために, Arrayの[[DefineOwnProperty]]を特殊化しており, この結果, 固定長を享受することができます.

Arrayの[[DefineOwnProperty]]の中から, 対象のProperty Name(P)がArray indexだった時の処理(Pがuint32_tでかつUINT32_MAXでない)を抜き出しました.
section 15.4.5.1 より抜粋

  • 4. Else if P is an array index (15.4), then
    • a. Let index be ToUint32(P).
    • b. Reject if index ≥ oldLen and oldLenDesc.[[Writable]] is false.
    • c. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing P, Desc, and false as arguments.
    • d. Reject if succeeded is false.
    • e. If index ≥ oldLen
      • i. Set oldLenDesc.[[Value]] to index + 1.
      • ii. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", oldLenDesc, and false as arguments. This call will always return true.
    • f. Return true.

これの肝はstep 4-bです. [[Writable]]がfalseの時, indexがold length以上なら問答無用でrejectされます. つまり,

// aryのlengthは[[Writable]]:falseかつ長さ10とする
ary[10] = "OK";  // これがrejectされる. strict modeでなければスルーされてsetされない

というふうに長さ以上のものがrejectされるので, 望みどおり, 固定長となります. pushとかその他もろもろもすべてこの[[DefineOwnProperty]]を使ってdefineするので, もうここで防がれたら, なんとしても伸ばすことはできません.
lengthの[[Configurable]]: falseは元々なので, [[Writable]]:falseになってしまえば, もう[[Writable]]:trueに戻すこともできないので, 絶対にlengthが変化することはありません.

結果例えば,

function assert(test) {
  if (!test) {
    throw "ERROR";
  }
}

function assertThrow(func) {
  var flag = false;
  try {
    func();
  } catch (e) {
    flag = true;
  }
  assert(flag);
}

function FixedArray(num) {
  var ary = new Array(num);
  Object.defineProperty(ary, "length", { writable: false });  // writable属性のみfalseへ
  return ary;
}

var fixed = FixedArray(10);
assert(fixed.length === 10);
assertThrow(function() {
  // section 15.4.4.7 Array.prototype.push
  // Put with throw=trueが
  // ArrayのDefineOwnProperty(section 15.4.5.1 step 4-b)に引っかかる
  fixed.push(10);
});
assert(fixed.length === 10);
assert(fixed[10] === undefined);
fixed[10] = 20;
assert(fixed.length === 10);
assert(fixed[10] === undefined);

// 10より下なら通常通り使える
fixed[0] = 'OK';
assert(fixed[0] === 'OK');

という風に利用できます.

悲しい話

現行engineでは利用できません.(なのに紹介するあたりが仕様厨たる所以)

SpiderMonkeyは未実装で, InternalErrorが出ます.

InternalError: defining the length property on the array is not currently supported

optimizedな状態でlength propertyへの変更を許すのは実装がすごく面倒になります...

JavaScriptCore, Carakan, V8の場合は, lengthを[[Writable]]: falseにしたにもかかわらず, 変更できちゃったり増えちゃったりします. (というかJSCの場合は[[Writable]]:falseになりません, Operaの場合は, lengthへの代入は防がれますが, ary[0] = 10;とかすると, lengthが1に勝手に増えています, V8も同様です.)

将来に期待!

補足

lv5は実装してます. (理由: 仕様厨が効率無視で作っているから)
ただ, optimizedな状態を保ったままこの機能を実現するために, JSArrayの[[DefineOwnProperty]]がすごいびろびろびろびろとcodeが伸びました... 我ながらひどいcode...
https://github.com/Constellation/iv/blob/master/src/lv5/jsarray.h

Remove all ads