屬性描述對象

概述

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

下面是屬性描述對象的一個實(shí)例。

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

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

(1)value

value存放該屬性的屬性值,默認(rèn)為undefined

(2)writable

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

(3)enumerable

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

(4)configurable

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

(5)get

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

(6)set

set存放一個函數(shù),表示該屬性的存值函數(shù)(setter),默認(rèn)為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
// 因?yàn)閣ritable為false,所以無法改變該屬性的值

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

需要注意的是,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屬性,會報(bào)錯。

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é)果就報(bào)錯。

Object.defineProperty()Object.defineProperties()的第三個參數(shù),是一個屬性對象。它的writableconfigurableenumerable這三個屬性的默認(rèn)值都為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é)果,其他元屬性都是默認(rèn)值。

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屬性默認(rèn)為false,導(dǎo)致無法對p屬性重新賦值,但是不會報(bào)錯(嚴(yán)格模式下會報(bào)錯)。不過,如果再一次使用Object.defineProperty方法對value屬性賦值,就會報(bào)錯。

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

var o = {};

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

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

上面代碼中,由于configurable屬性默認(rèn)為false,導(dǎo)致無法刪除某個屬性。

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屬性默認(rèn)為false,所以不出現(xiàn)在for...in循環(huán)中。

元屬性

屬性描述對象的屬性,被稱為“元屬性”,因?yàn)樗梢钥醋魇强刂茖傩缘膶傩浴?/p>

可枚舉性(enumerable)

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

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

上面代碼中,toString不是obj對象自身的屬性,但是in運(yùn)算符也返回true,導(dǎo)致被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,所以一般的遍歷操作都無法獲取該屬性,使得它有點(diǎn)像“秘密”屬性,但不是真正的私有屬性,還是可以直接獲取它的值。

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

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

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

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)決定了是否可以修改屬性描述對象。也就是說,當(dāng)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。然后,逐一改動value、writableenumerableconfigurable,結(jié)果都報(bào)錯。

需要注意的是,writable只有在從false改為true會報(bào)錯,從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時,直接對該屬性賦值,不報(bào)錯,但不會成功。

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就無法刪除。

需要注意的是,當(dāng)使用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是等價(jià)的寫法。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的屬性賦值不會報(bào)錯,只會默默失敗。但是,嚴(yán)格模式下會報(bào)錯,即使是對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,也不可以再自定義這個屬性了。在嚴(yán)格模式下,這樣做還會拋出一個錯誤。但是,有一個規(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ù)組實(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ù)組的實(shí)例對象([])沒有可枚舉屬性,不可枚舉屬性有l(wèi)ength;Object.prototype對象也沒有可枚舉屬性,但是有不少不可枚舉屬性。

Object.prototype.propertyIsEnumerable()

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

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

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

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

存取器(accessor)

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

存取器提供的是虛擬屬性,即該屬性的值不是實(shí)際存在的,而是每次讀取時計(jì)算生成的。利用這個功能,可以實(shí)現(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 '新的值必須大于當(dāng)前值';
  }
};

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的實(shí)例對象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ù)。

利用存取器,可以實(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屬性,實(shí)現(xiàn)了綁定。兩者之中只要有一個對象發(fā)生變化,就能在另一個對象上實(shí)時反映出來。

對象的拷貝

有時,我們需要將一個對象的所有屬性,拷貝到另一個對象。ES5沒有提供這個方法,必須自己實(shí)現(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,但是實(shí)際上網(wǎng)頁的背景色并不會變紅。但是,如果用第一種簡單拷貝的方法,反而能夠達(dá)到目的。這提示我們,可以把兩種方法結(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),防止對象被改變。最弱一層的保護(hù)是Object.preventExtensions,其次是Object.seal,最強(qiáng)的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

如果是在嚴(yán)格模式下,則會拋出一個錯誤。

(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實(shí)質(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屬性就會報(bào)錯。

可寫性(writable)有點(diǎn)特別。如果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è)置將報(bào)錯;如果原為true,則不會有問題。

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

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

上面代碼中,Object.seal方法對p屬性的value無效,是因?yàn)榇藭rp屬性的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方法可以使得一個對象無法添加新屬性、無法刪除舊屬性、也無法改變屬性的值,使得這個對象實(shí)際上變成了常量。

var o = {
  p: 'hello'
};

Object.freeze(o);

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

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

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

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é),再對它的屬性賦值,在嚴(yán)格模式下會報(bào)錯。Object.isFrozen方法可以防止發(fā)生這樣的錯誤。

var obj = {
  p: 'hello'
};

Object.freeze(obj);

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

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

局限性

上面的方法鎖定對象的可寫性有一個漏洞,依然可以通過改變原型對象,來為對象增加屬性。

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

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).hello = "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ù)組是可以改變的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 概述 JavaScript提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述一個對象的屬性的行為,控制它的行為。這被稱為“屬性描述對...
    zjh111閱讀 734評論 0 0
  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,614評論 0 5
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 2,606評論 9 22
  • 《愛你真的好難》 長篇連載之16 作者:金小貝 耿震和龔小南就這樣聊了整整一下午,兩個人都毫無...
    金小貝127閱讀 341評論 0 3
  • 2017年7月11日 聚焦網(wǎng)初五原創(chuàng) 張婷 鄭州 分享第三十九天 星期二 慢就是快,慢、不急,自然就是...
    心愿幸福閱讀 461評論 0 0