JS中的属性描述对象 6

控制对象状态

JavaScript提供了三种方法,精确控制一个对象的读写状态,防止对象被改变。最弱一层的保护是Object.preventExtensions,其次是Object.seal,最强的Object.freeze。

Object.preventExtensions()

Object.preventExtensions方法可以使得一个对象无法再添加新的属性。

var o = new Object();

Object.preventExtensions(o);

Object.defineProperty(o, 'p', {
  value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.

o.p = 1;
o.p // undefined

如果是在严格模式下,则会抛出一个错误。

(function () {
  'use strict';
  o.p = '1'
}());
// TypeError: Can't add property bar, object is not extensible

不过,对于使用了preventExtensions方法的对象,可以用delete命令删除它的现有属性。

var o = new Object();
o.p = 1;

Object.preventExtensions(o);

delete o.p;
o.p // undefined

Object.isExtensible()

Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法。也就是说,检查是否可以为一个对象添加属性。

var o = new Object();

Object.isExtensible(o) // true
Object.preventExtensions(o);
Object.isExtensible(o) // false

上面代码新生成了一个o对象,对该对象使用Object.isExtensible方法,返回true,表示可以添加新属性。对该对象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已经不能添加新属性了。

Object.seal()

Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。

var o = {
  p: 'hello'
};

Object.seal(o);

delete o.p;
o.p // "hello"

o.x = 'world';
o.x // undefined

上面代码中,一个对象执行Object.seal方法以后,就无法添加新属性和删除旧属性了。

Object.seal实质是把属性描述对象的configurable属性设为false,因此属性描述对象不再能改变了。

var o = {
  p: 'a'
};

// seal方法之前
Object.getOwnPropertyDescriptor(o, 'p')
// Object {
//   value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Object.seal(o);

// seal方法之后
Object.getOwnPropertyDescriptor(o, 'p')
// Object {
//   value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: false
// }

Object.defineProperty(o, 'p', {
  enumerable: false
})
// TypeError: Cannot redefine property: p

上面代码中,使用Object.seal方法之后,属性描述对象的configurable属性就变成了false,然后改变enumerable属性就会报错。

可写性(writable)有点特别。如果writable为false,使用Object.seal方法以后,将无法将其变成true;但是,如果writable为true,依然可以将其变成false。

var o1 = Object.defineProperty({}, 'p', {
  writable: false
});
Object.seal(o1);
Object.defineProperty(o1, 'p', {
  writable:true
})
// Uncaught TypeError: Cannot redefine property: p

var o2 = Object.defineProperty({}, 'p', {
  writable: true
});
Object.seal(o2);
Object.defineProperty(o2, 'p', {
  writable:false
});

Object.getOwnPropertyDescriptor(o2, 'p')
// {
//   value: '',
//   writable: false,
//   enumerable: true,
//   configurable: false
// }

上面代码中,同样是使用了Object.seal方法,如果writable原为false,改变这个设置将报错;如果原为true,则不会有问题。

至于属性对象的value是否可改变,是由writable决定的。

var o = { p: 'a' };
Object.seal(o);
o.p = 'b';
o.p // 'b'

上面代码中,Object.seal方法对p属性的value无效,是因为此时p属性的writable为true。

Object.isSealed()

Object.isSealed方法用于检查一个对象是否使用了Object.seal方法。

var o = { p: 'a' };

Object.seal(o);
Object.isSealed(o) // true

这时,Object.isExtensible方法也返回false。

var o = { p: 'a' };

Object.seal(o);
Object.isExtensible(o) // false

Object.freeze()

Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量。

var o = {
  p: 'hello'
};

Object.freeze(o);

o.p = 'world';
o.p // hello

o.t = 'hello';
o.t // undefined

上面代码中,对现有属性重新赋值(o.p = 'world')或者添加一个新属性,并不会报错,只是默默地失败。但是,如果是在严格模式下,就会报错。

var o = {
  p: 'hello'
};

Object.freeze(o);

// 对现有属性重新赋值
(function () {
  'use strict';
  o.p = 'world';
}())
// TypeError: Cannot assign to read only property 'p' of #

// 添加不存在的属性
(function () {
  'use strict';
  o.t = 123;
}())
// TypeError: Can't add property t, object is not extensible

Object.isFrozen()

Object.isFrozen方法用于检查一个对象是否使用了Object.freeze()方法。

var obj = {
  p: 'hello'
};

Object.freeze(obj);
Object.isFrozen(obj) // true

前面说过,如果一个对象被冻结,再对它的属性赋值,在严格模式下会报错。Object.isFrozen方法可以防止发生这样的错误。

var obj = {
  p: 'hello'
};

Object.freeze(obj);

if (!Object.isFrozen(obj)) {
  obj.p = 'world';
}

上面代码中,确认obj没有被冻结后,再对它的属性赋值,就不会报错了。

局限性

上面的方法锁定对象的可写性有一个漏洞,依然可以通过改变原型对象,来为对象增加属性。

var obj = new Object();
Object.preventExtensions(obj);

var proto = Object.getPrototypeOf(obj);
//获取对象原型
proto.t = 'hello';
obj.t
// hello

一种解决方案是,把原型也冻结住。

var obj = Object.seal(
  Object.create(
    Object.freeze({x: 1}),
    {
      y: {
        value: 2,
        writable: true
      }
    }
  )
);

Object.getPrototypeOf(obj).hello = "hello";
obj.hello // undefined

另外一个局限是,如果属性值是对象,上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容。

var obj = {
  foo: 1,
  bar: ['a', 'b']
};
Object.freeze(obj);

obj.bar.push('c');
obj.bar // ["a", "b", "c"]

上面代码中,obj.bar属性指向一个数组,obj对象被冻结以后,这个指向无法改变,即无法指向其他值,但是所指向的数组是可以改变的。

你可能感兴趣的:(JS中的属性描述对象 6)