Object.defineProperty できなくなるケース

ES-Discussメーリングリストで知った。

Object.prototype.get = function(){};

var o = {};
Object.defineProperty(o, "hoge", { value: "OK" });
// TypeError: property descriptors must not specify a value or be writable when a getter or setter has been specified

Object.prototypeget, set あたりのプロパティを追加してしまうと、Object.defineProperty()時に例外が発生していしまう件、というやつ。

Object.prototype に設定すべきでないプロパティ

先に、設定すべきでないプロパティをあげておく

何故か

Object.definePropertyの第3引数にはDescriptorという特定のプロパティを持つオブジェクトを指定することになる。このDescriptorは以下の2種類がある。

DataDescriptor
普通のプロパティ
AccessorDescriptor
Getter や Setter

そして、互いに存在してはいけないプロパティがある。DataDescriptorにはget,setを持っていてはいけないし、AccessorDescriptorvalue,writableを持ってはいけない、という具合だ。

defineProperty時にはこの検証が行われるわけだが、問題なのはこのプロパティを持っているかの検証に、[[HasProperty]]を使用する点だ。[[HasProperty]]は、[[Prototype]]内も見る内部関数なので、Object.prototype.*内も見てしまう事だ。これが[[GetOwnProperty]]による検証だったら問題はなかっただろう…。

防ぐには

Object.prototype への追加を禁止する。

Object.freeze(Object.prototype);

Object.create(null) からデフォルトのdescriptorを作る

function hasOwn(obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
}
function defineProperty(obj, key, desc) {
  var d = Object.create(null);
  d.configurable = hasOwn(desc,"configurable") ? desc.configurable : false;
  d.enumerable = hasOwn(desc, "enumrable") ? desc.enumerable : false;
  if (hasOwn(desc, "value")) {
    d.writable = hasOwn(desc, "writable") ? desc.writable : false
    d.value = desc.value;
  } else {
    d.get = hasOwn(desc, "get") ? desc.get : undefined;
    d.set = hasOwn(desc, "set") ? desc.set : undefined;
  }
  return Object.defineProperty(obj, key, d);
}

くらいしか思いつかん。

同様の事は、Object.createの第2引数指定時、Object.defineProperties()にも起こりえる。

皆さん、注意しましょう。

# これ、JavaScript sucks 入りじゃね?