JavaScript 屬性描述對象

2018-07-24 11:51 更新

目錄

概述

JavaScript提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述一個對象的屬性的行為,控制它的行為。這被稱為“屬性描述對象”(attributes object)。每個屬性都有自己對應(yīng)的屬性描述對象,保存該屬性的一些元信息。

下面是屬性描述對象的一個實例。

{
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false,
  get: undefined,
  set: undefined
}

屬性描述對象提供6個元屬性。

(1)value

value存放該屬性的屬性值,默認為undefined。

(2)writable

writable存放一個布爾值,表示屬性值(value)是否可改變,默認為true

(3)enumerable

enumerable存放一個布爾值,表示該屬性是否可枚舉,默認為true。如果設(shè)為false,會使得某些操作(比如for...in循環(huán)、Object.keys())跳過該屬性。

(4)configurable

configurable存放一個布爾值,表示“可配置性”,默認為true。如果設(shè)為false,將阻止某些操作改寫該屬性,比如,無法刪除該屬性,也不得改變該屬性的屬性描述對象(value屬性除外)。也就是說,configurable屬性控制了屬性描述對象的可寫性。

(5)get

get存放一個函數(shù),表示該屬性的取值函數(shù)(getter),默認為undefined。

(6)set

set存放一個函數(shù),表示該屬性的存值函數(shù)(setter),默認為undefined

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor方法可以讀出對象自身屬性的屬性描述對象。

var o = { p: 'a' };

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

上面代碼表示,使用Object.getOwnPropertyDescriptor方法,讀取o對象的p屬性的屬性描述對象。

Object.defineProperty(),Object.defineProperties()

Object.defineProperty方法允許通過定義屬性描述對象,來定義或修改一個屬性,然后返回修改后的對象。它的格式如下。

Object.defineProperty(object, propertyName, attributesObject)

上面代碼中,Object.defineProperty方法接受三個參數(shù),第一個是屬性所在的對象,第二個是屬性名(它應(yīng)該是一個字符串),第三個是屬性的描述對象。比如,新建一個o對象,并定義它的p屬性,寫法如下。

var o = Object.defineProperty({}, 'p', {
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false
});

o.p
// 123

o.p = 246;
o.p
// 123
// 因為writable為false,所以無法改變該屬性的值

如果屬性已經(jīng)存在,Object.defineProperty方法相當于更新該屬性的屬性描述對象。

需要注意的是,Object.defineProperty方法和后面的Object.defineProperties方法,都有性能損耗,會拖慢執(zhí)行速度,不宜大量使用。

如果一次性定義或修改多個屬性,可以使用Object.defineProperties方法。

var o = Object.defineProperties({}, {
  p1: { value: 123, enumerable: true },
  p2: { value: 'abc', enumerable: true },
  p3: { get: function () { return this.p1 + this.p2 },
    enumerable:true,
    configurable:true
  }
});

o.p1 // 123
o.p2 // "abc"
o.p3 // "123abc"

上面代碼中的p3屬性,定義了取值函數(shù)get。這時需要注意的是,一旦定義了取值函數(shù)get(或存值函數(shù)set),就不能將writable設(shè)為true,或者同時定義value屬性,會報錯。

var o = {};

Object.defineProperty(o, 'p', {
  value: 123,
  get: function() { return 456; }
});
// TypeError: Invalid property.
// A property cannot both have accessors and be writable or have a value,

上面代碼同時定義了get屬性和value屬性,結(jié)果就報錯。

Object.defineProperty()Object.defineProperties()的第三個參數(shù),是一個屬性對象。它的writable、configurableenumerable這三個屬性的默認值都為false。

var obj = {};
Object.defineProperty(obj, 'foo', { configurable: true });
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
//   value: undefined,
//   writable: false,
//   enumerable: false,
//   configurable: true
// }

上面代碼中,定義obj對象的foo屬性時,只定義了可配置性configurabletrue。結(jié)果,其他元屬性都是默認值。

writable屬性為false,表示對應(yīng)的屬性的值將不得改寫。

var o = {};

Object.defineProperty(o, 'p', {
  value: "bar"
});

o.p // bar

o.p = 'foobar';
o.p // bar

Object.defineProperty(o, 'p', {
  value: 'foobar',
});
// TypeError: Cannot redefine property: p

上面代碼由于writable屬性默認為false,導致無法對p屬性重新賦值,但是不會報錯(嚴格模式下會報錯)。不過,如果再一次使用Object.defineProperty方法對value屬性賦值,就會報錯。

configurable屬性為false,將無法刪除該屬性,也無法修改attributes對象(value屬性除外)。

var o = {};

Object.defineProperty(o, 'p', {
  value: 'bar',
});

delete o.p
o.p // "bar"

上面代碼中,由于configurable屬性默認為false,導致無法刪除某個屬性。

enumerable屬性為false,表示對應(yīng)的屬性不會出現(xiàn)在for...in循環(huán)和Object.keys方法中。

var o = {
  p1: 10,
  p2: 13,
};

Object.defineProperty(o, 'p3', {
  value: 3,
});

for (var i in o) {
  console.log(i, o[i]);
}
// p1 10
// p2 13

上面代碼中,p3屬性是用Object.defineProperty方法定義的,由于enumerable屬性默認為false,所以不出現(xiàn)在for...in循環(huán)中。

元屬性

屬性描述對象的屬性,被稱為“元屬性”,因為它可以看作是控制屬性的屬性。

可枚舉性(enumerable)

JavaScript的最初版本,in 運算符和基于它的for...in循環(huán),會遍歷對象實例的所有屬性,包括繼承的屬性。

var obj = {};
'toString' in obj // true

上面代碼中,toString不是obj對象自身的屬性,但是in運算符也返回true,導致被for...in循環(huán)遍歷,這顯然不太合理。后來就引入了“可枚舉性”這個概念,只有可枚舉的屬性,才會被for...in循環(huán)遍歷,同時還規(guī)定原生繼承的屬性都是不可枚舉的,這樣就保證了for...in循環(huán)的可用性。

可枚舉性(enumerable)用來控制所描述的屬性,是否將被包括在for...in循環(huán)之中。具體來說,如果一個屬性的enumerablefalse,下面三個操作不會取到該屬性。

  • for..in循環(huán)
  • Object.keys方法
  • JSON.stringify方法

因此,enumerable可以用來設(shè)置“秘密”屬性。

var o = {a: 1, b: 2};

o.c = 3;
Object.defineProperty(o, 'd', {
  value: 4,
  enumerable: false
});

o.d // 4

for (var key in o) {
  console.log(o[key]);
}
// 1
// 2
// 3

Object.keys(o)  // ["a", "b", "c"]

JSON.stringify(o) // "{a:1, b:2, c:3}"

上面代碼中,d屬性的enumerablefalse,所以一般的遍歷操作都無法獲取該屬性,使得它有點像“秘密”屬性,但不是真正的私有屬性,還是可以直接獲取它的值。

基本上,JavaScript原生提供的屬性都是不可枚舉的,用戶自定義的屬性都是可枚舉的。

與枚舉性相關(guān)的幾個操作的區(qū)別的是,for...in循環(huán)包括繼承自原型對象的屬性,Object.keys方法只返回對象本身的屬性。如果需要獲取對象自身的所有屬性,不管是否可枚舉,可以使用Object.getOwnPropertyNames方法,詳見下文。

考慮到JSON.stringify方法會排除enumerablefalse的值,有時可以利用這一點,為對象添加注釋信息。

var car = {
  id: 123,
  color: 'red',
  ownerId: 12
};

var owner = {
  id: 12,
  name: 'Jack'
};

Object.defineProperty(car, 'ownerInfo', {
  value: owner,
  enumerable: false
});

car.ownerInfo
// {id: 12, name: "Jack"}

JSON.stringify(car)
//  "{"id": 123,"color": "red","ownerId": 12}"

上面代碼中,owner對象作為注釋部分,加入car對象。由于ownerInfo屬性不可枚舉,所以JSON.stringify方法最后輸出car對象時,會忽略ownerInfo屬性。

這提示我們,如果你不愿意某些屬性出現(xiàn)在JSON輸出之中,可以把它的enumerable屬性設(shè)為false。

可配置性(configurable)

可配置性(configurable)決定了是否可以修改屬性描述對象。也就是說,當configurablefalse的時候,value、writable、enumerable和configurable都不能被修改了。

var o = Object.defineProperty({}, 'p', {
  value: 1,
  writable: false,
  enumerable: false,
  configurable: false
});

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

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

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

Object.defineProperties(o,'p',{configurable: true})
// TypeError: Cannot redefine property: p

上面代碼首先定義對象o,并且定義o的屬性pconfigurablefalse。然后,逐一改動valuewritable、enumerable、configurable,結(jié)果都報錯。

需要注意的是,writable只有在從false改為true會報錯,從true改為false則是允許的。

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

Object.defineProperty(o,'p', {writable: false})
// 修改成功

至于value,只要writableconfigurable有一個為true,就允許改動。

var o1 = Object.defineProperty({}, 'p', {
  value: 1,
  writable: true,
  configurable: false
});

Object.defineProperty(o1,'p', {value: 2})
// 修改成功

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

Object.defineProperty(o2,'p', {value: 2})
// 修改成功

另外,configurablefalse時,直接對該屬性賦值,不報錯,但不會成功。

var o = Object.defineProperty({}, 'p', {
  value: 1,
  configurable: false
});

o.p = 2;
o.p // 1

上面代碼中,o對象的p屬性是不可配置的,對它賦值是不會生效的。

可配置性決定了一個變量是否可以被刪除(delete)。

var o = Object.defineProperties({}, {
  p1: { value: 1, configurable: true },
  p2: { value: 2, configurable: false }
});

delete o.p1 // true
delete o.p2 // false

o.p1 // undefined
o.p2 // 2

上面代碼中的對象o有兩個屬性,p1是可配置的,p2是不可配置的。結(jié)果,p2就無法刪除。

需要注意的是,當使用var命令聲明變量時,變量的configurablefalse。

var a1 = 1;

Object.getOwnPropertyDescriptor(this,'a1')
// Object {
//  value: 1,
//  writable: true,
//  enumerable: true,
//  configurable: false
// }

而不使用var命令聲明變量時(或者使用屬性賦值的方式聲明變量),變量的可配置性為true。

a2 = 1;

Object.getOwnPropertyDescriptor(this,'a2')
// Object {
//  value: 1,
//  writable: true,
//  enumerable: true,
//  configurable: true
// }

// 或者寫成

window.a3 = 1;

Object.getOwnPropertyDescriptor(window, 'a3')
// Object {
//  value: 1,
//  writable: true,
//  enumerable: true,
//  configurable: true
// }

上面代碼中的this.a3 = 1a3 = 1是等價的寫法。window指的是瀏覽器的頂層對象。

這種差異意味著,如果一個變量是使用var命令生成的,就無法用delete命令刪除。也就是說,delete只能刪除對象的屬性。

var a1 = 1;
a2 = 1;

delete a1 // false
delete a2 // true

a1 // 1
a2 // ReferenceError: a2 is not defined

可寫性(writable)

可寫性(writable)決定了屬性的值(value)是否可以被改變。

var o = {};

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

o.a // 37
o.a = 25;
o.a // 37

上面代碼將o對象的a屬性可寫性設(shè)為false,然后改變這個屬性的值,就不會有任何效果。

注意,正常模式下,對可寫性為false的屬性賦值不會報錯,只會默默失敗。但是,嚴格模式下會報錯,即使是對a屬性重新賦予一個同樣的值。

關(guān)于可寫性,還有一種特殊情況。就是如果原型對象的某個屬性的可寫性為false,那么派生對象將無法自定義這個屬性。

var proto = Object.defineProperty({}, 'foo', {
  value: 'a',
  writable: false
});

var o = Object.create(proto);

o.foo = 'b';
o.foo // 'a'

上面代碼中,對象protofoo屬性不可寫,結(jié)果proto的派生對象o,也不可以再自定義這個屬性了。在嚴格模式下,這樣做還會拋出一個錯誤。但是,有一個規(guī)避方法,就是通過覆蓋屬性描述對象,繞過這個限制,原因是這種情況下,原型鏈會被完全忽視。

Object.defineProperty(o, 'foo', {
  value: 'b'
});

o.foo // 'b'

Object.getOwnPropertyNames()

Object.getOwnPropertyNames方法返回直接定義在某個對象上面的全部屬性的名稱,而不管該屬性是否可枚舉。

var o = Object.defineProperties({}, {
  p1: { value: 1, enumerable: true },
  p2: { value: 2, enumerable: false }
});

Object.getOwnPropertyNames(o)
// ["p1", "p2"]

一般來說,系統(tǒng)原生的屬性(即非用戶自定義的屬性)都是不可枚舉的。

// 比如,數(shù)組實例自帶length屬性是不可枚舉的
Object.keys([]) // []
Object.getOwnPropertyNames([]) // [ 'length' ]

// Object.prototype對象的自帶屬性也都是不可枚舉的
Object.keys(Object.prototype) // []
Object.getOwnPropertyNames(Object.prototype)
// ['hasOwnProperty',
//  'valueOf',
//  'constructor',
//  'toLocaleString',
//  'isPrototypeOf',
//  'propertyIsEnumerable',
//  'toString']

上面代碼可以看到,數(shù)組的實例對象([])沒有可枚舉屬性,不可枚舉屬性有l(wèi)ength;Object.prototype對象也沒有可枚舉屬性,但是有不少不可枚舉屬性。

Object.prototype.propertyIsEnumerable()

對象實例的propertyIsEnumerable方法用來判斷一個屬性是否可枚舉。

var o = {};
o.p = 123;

o.propertyIsEnumerable('p') // true
o.propertyIsEnumerable('toString') // false

上面代碼中,用戶自定義的p屬性是可枚舉的,而繼承自原型對象的toString屬性是不可枚舉的。

存取器(accessor)

除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函數(shù)稱為setter,使用set命令;取值函數(shù)稱為getter,使用get命令。

存取器提供的是虛擬屬性,即該屬性的值不是實際存在的,而是每次讀取時計算生成的。利用這個功能,可以實現(xiàn)許多高級特性,比如每個屬性禁止賦值。

var o = {
  get p() {
    return 'getter';
  },
  set p(value) {
    console.log('setter: ' + value);
  }
};

上面代碼中,o對象內(nèi)部的getset命令,分別定義了p屬性的取值函數(shù)和存值函數(shù)。定義了這兩個函數(shù)之后,對p屬性取值時,取值函數(shù)會自動調(diào)用;對p屬性賦值時,存值函數(shù)會自動調(diào)用。

o.p // "getter"
o.p = 123 // "setter: 123"

注意,取值函數(shù)Getter不能接受參數(shù),存值函數(shù)Setter只能接受一個參數(shù)(即屬性的值)。另外,對象也不能有與取值函數(shù)同名的屬性。比如,上面的對象o設(shè)置了取值函數(shù)p以后,就不能再另外定義一個p屬性。

存取器往往用于,屬性的值需要依賴對象內(nèi)部數(shù)據(jù)的場合。

var o ={
  $n : 5,
  get next() { return this.$n++ },
  set next(n) {
    if (n >= this.$n) this.$n = n;
    else throw '新的值必須大于當前值';
  }
};

o.next // 5

o.next = 10;
o.next // 10

上面代碼中,next屬性的存值函數(shù)和取值函數(shù),都依賴于對內(nèi)部屬性$n的操作。

存取器也可以通過Object.defineProperty定義。

var d = new Date();

Object.defineProperty(d, 'month', {
  get: function () {
    return d.getMonth();
  },
  set: function (v) {
    d.setMonth(v);
  }
});

上面代碼為Date的實例對象d,定義了一個可讀寫的month屬性。

存取器也可以使用Object.create方法定義。

var o = Object.create(Object.prototype, {
  foo: {
    get: function () {
      return 'getter';
    },
    set: function (value) {
      console.log('setter: '+value);
    }
  }
});

如果使用上面這種寫法,屬性foo必須定義一個屬性描述對象。該對象的getset屬性,分別是foo的取值函數(shù)和存值函數(shù)。

利用存取器,可以實現(xiàn)數(shù)據(jù)對象與DOM對象的雙向綁定。

Object.defineProperty(user, 'name', {
  get: function () {
    return document.getElementById('foo').value;
  },
  set: function (newValue) {
    document.getElementById('foo').value = newValue;
  },
  configurable: true
});

上面代碼使用存取函數(shù),將DOM對象foo與數(shù)據(jù)對象username屬性,實現(xiàn)了綁定。兩者之中只要有一個對象發(fā)生變化,就能在另一個對象上實時反映出來。

對象的拷貝

有時,我們需要將一個對象的所有屬性,拷貝到另一個對象。ES5沒有提供這個方法,必須自己實現(xiàn)。

var extend = function (to, from) {
  for (var property in from) {
    to[property] = from[property];
  }

  return to;
}

extend({}, {
  a: 1
})
// {a: 1}

上面這個方法的問題在于,如果遇到存取器定義的屬性,會只拷貝值。

extend({}, {
  get a() { return 1 }
})
// {a: 1}

為了解決這個問題,我們可以通過Object.defineProperty方法來拷貝屬性。

var extend = function (to, from) {
  for (var property in from) {
    Object.defineProperty(
      to,
      property,
      Object.getOwnPropertyDescriptor(from, property)
    );
  }

  return to;
}

extend({}, { get a(){ return 1 } })
// { get a(){ return 1 } })

這段代碼還是有問題,拷貝某些屬性時會失效。

extend(document.body.style, {
  backgroundColor: "red"
});

上面代碼的目的是,設(shè)置document.body.style.backgroundColor屬性為red,但是實際上網(wǎng)頁的背景色并不會變紅。但是,如果用第一種簡單拷貝的方法,反而能夠達到目的。這提示我們,可以把兩種方法結(jié)合起來,對于簡單屬性,就直接拷貝,對于那些通過屬性描述對象設(shè)置的屬性,則使用Object.defineProperty方法拷貝。

var extend = function (to, from) {
  for (var property in from) {
    var descriptor = Object.getOwnPropertyDescriptor(from, property);

    if (descriptor && ( !descriptor.writable
      || !descriptor.configurable
      || !descriptor.enumerable
      || descriptor.get
      || descriptor.set)) {
      Object.defineProperty(to, property, descriptor);
    } else {
      to[property] = from[property];
    }
  }
}

上面的這段代碼,可以很好地拷貝對象所有可遍歷(enumerable)的屬性。

控制對象狀態(tài)

JavaScript提供了三種方法,精確控制一個對象的讀寫狀態(tài),防止對象被改變。最弱一層的保護是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命令刪除它的現(xiàn)有屬性。

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,表示已經(jīng)不能添加新屬性了。

Object.seal()

Object.seal方法使得一個對象既無法添加新屬性,也無法刪除舊屬性。

var o = {
  p: 'hello'
};

Object.seal(o);

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

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

上面代碼中,一個對象執(zhí)行Object.seal方法以后,就無法添加新屬性和刪除舊屬性了。

Object.seal實質(zhì)是把屬性描述對象的configurable屬性設(shè)為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)有點特別。如果writablefalse,使用Object.seal方法以后,將無法將其變成true;但是,如果writabletrue,依然可以將其變成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,改變這個設(shè)置將報錯;如果原為true,則不會有問題。

至于屬性對象的value是否可改變,是由writable決定的。

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

上面代碼中,Object.seal方法對p屬性的value無效,是因為此時p屬性的writabletrue。

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

上面代碼中,對現(xiàn)有屬性重新賦值(o.p = 'world')或者添加一個新屬性,并不會報錯,只是默默地失敗。但是,如果是在嚴格模式下,就會報錯。

var o = {
  p: 'hello'
};

Object.freeze(o);

// 對現(xiàn)有屬性重新賦值
(function () {
  'use strict';
  o.p = 'world';
}())
// TypeError: Cannot assign to read only property 'p' of #<Object>

// 添加不存在的屬性
(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

前面說過,如果一個對象被凍結(jié),再對它的屬性賦值,在嚴格模式下會報錯。Object.isFrozen方法可以防止發(fā)生這樣的錯誤。

var obj = {
  p: 'hello'
};

Object.freeze(obj);

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

上面代碼中,確認obj沒有被凍結(jié)后,再對它的屬性賦值,就不會報錯了。

局限性

需要注意的是,使用上面這些方法鎖定對象的可寫性,但是依然可以通過改變該對象的原型對象,來為它增加屬性。

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

var proto = Object.getPrototypeOf(obj);
proto.t = 'hello';
obj.t
// hello

一種解決方案是,把原型也凍結(jié)住。

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

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

另外一個局限是,如果屬性值是對象,上面這些方法只能凍結(jié)屬性指向的對象,而不能凍結(jié)對象本身的內(nèi)容。

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

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

上面代碼中,obj.bar屬性指向一個數(shù)組,obj對象被凍結(jié)以后,這個指向無法改變,即無法指向其他值,但是所指向的數(shù)組是可以改變的。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號