W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
JavaScript提供了一個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來(lái)描述一個(gè)對(duì)象的屬性的行為,控制它的行為。這被稱為“屬性描述對(duì)象”(attributes object)。每個(gè)屬性都有自己對(duì)應(yīng)的屬性描述對(duì)象,保存該屬性的一些元信息。
下面是屬性描述對(duì)象的一個(gè)實(shí)例。
{
value: 123,
writable: false,
enumerable: true,
configurable: false,
get: undefined,
set: undefined
}
屬性描述對(duì)象提供6個(gè)元屬性。
(1)value
value
存放該屬性的屬性值,默認(rèn)為undefined
。
(2)writable
writable
存放一個(gè)布爾值,表示屬性值(value)是否可改變,默認(rèn)為true
。
(3)enumerable
enumerable
存放一個(gè)布爾值,表示該屬性是否可枚舉,默認(rèn)為true
。如果設(shè)為false
,會(huì)使得某些操作(比如for...in
循環(huán)、Object.keys()
)跳過(guò)該屬性。
(4)configurable
configurable
存放一個(gè)布爾值,表示“可配置性”,默認(rèn)為true
。如果設(shè)為false
,將阻止某些操作改寫該屬性,比如,無(wú)法刪除該屬性,也不得改變?cè)搶傩缘膶傩悦枋鰧?duì)象(value
屬性除外)。也就是說(shuō),configurable
屬性控制了屬性描述對(duì)象的可寫性。
(5)get
get
存放一個(gè)函數(shù),表示該屬性的取值函數(shù)(getter),默認(rèn)為undefined
。
(6)set
set
存放一個(gè)函數(shù),表示該屬性的存值函數(shù)(setter),默認(rèn)為undefined
。
Object.getOwnPropertyDescriptor
方法可以讀出對(duì)象自身屬性的屬性描述對(duì)象。
var o = { p: 'a' };
Object.getOwnPropertyDescriptor(o, 'p')
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
上面代碼表示,使用Object.getOwnPropertyDescriptor
方法,讀取o
對(duì)象的p
屬性的屬性描述對(duì)象。
Object.defineProperty
方法允許通過(guò)定義屬性描述對(duì)象,來(lái)定義或修改一個(gè)屬性,然后返回修改后的對(duì)象。它的格式如下。
Object.defineProperty(object, propertyName, attributesObject)
上面代碼中,Object.defineProperty
方法接受三個(gè)參數(shù),第一個(gè)是屬性所在的對(duì)象,第二個(gè)是屬性名(它應(yīng)該是一個(gè)字符串),第三個(gè)是屬性的描述對(duì)象。比如,新建一個(gè)o
對(duì)象,并定義它的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,所以無(wú)法改變?cè)搶傩缘闹?/span>
如果屬性已經(jīng)存在,Object.defineProperty
方法相當(dāng)于更新該屬性的屬性描述對(duì)象。
需要注意的是,Object.defineProperty
方法和后面的Object.defineProperties
方法,都有性能損耗,會(huì)拖慢執(zhí)行速度,不宜大量使用。
如果一次性定義或修改多個(gè)屬性,可以使用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í)需要注意的是,一旦定義了取值函數(shù)get
(或存值函數(shù)set
),就不能將writable
設(shè)為true
,或者同時(shí)定義value
屬性,會(huì)報(bào)錯(cuò)。
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,
上面代碼同時(shí)定義了get
屬性和value
屬性,結(jié)果就報(bào)錯(cuò)。
Object.defineProperty()
和Object.defineProperties()
的第三個(gè)參數(shù),是一個(gè)屬性對(duì)象。它的writable
、configurable
、enumerable
這三個(gè)屬性的默認(rèn)值都為false
。
var obj = {};
Object.defineProperty(obj, 'foo', { configurable: true });
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: undefined,
// writable: false,
// enumerable: false,
// configurable: true
// }
上面代碼中,定義obj
對(duì)象的foo
屬性時(shí),只定義了可配置性configurable
為true
。結(jié)果,其他元屬性都是默認(rèn)值。
writable
屬性為false
,表示對(duì)應(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)致無(wú)法對(duì)p
屬性重新賦值,但是不會(huì)報(bào)錯(cuò)(嚴(yán)格模式下會(huì)報(bào)錯(cuò))。不過(guò),如果再一次使用Object.defineProperty
方法對(duì)value
屬性賦值,就會(huì)報(bào)錯(cuò)。
configurable
屬性為false
,將無(wú)法刪除該屬性,也無(wú)法修改attributes
對(duì)象(value
屬性除外)。
var o = {};
Object.defineProperty(o, 'p', {
value: 'bar',
});
delete o.p
o.p // "bar"
上面代碼中,由于configurable
屬性默認(rèn)為false
,導(dǎo)致無(wú)法刪除某個(gè)屬性。
enumerable
屬性為false
,表示對(duì)應(yīng)的屬性不會(huì)出現(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)中。
屬性描述對(duì)象的屬性,被稱為“元屬性”,因?yàn)樗梢钥醋魇强刂茖傩缘膶傩浴?/p>
JavaScript的最初版本,in
運(yùn)算符和基于它的for...in
循環(huán),會(huì)遍歷對(duì)象實(shí)例的所有屬性,包括繼承的屬性。
var obj = {};
'toString' in obj // true
上面代碼中,toString
不是obj
對(duì)象自身的屬性,但是in
運(yùn)算符也返回true
,導(dǎo)致被for...in
循環(huán)遍歷,這顯然不太合理。后來(lái)就引入了“可枚舉性”這個(gè)概念,只有可枚舉的屬性,才會(huì)被for...in
循環(huán)遍歷,同時(shí)還規(guī)定原生繼承的屬性都是不可枚舉的,這樣就保證了for...in
循環(huán)的可用性。
可枚舉性(enumerable)用來(lái)控制所描述的屬性,是否將被包括在for...in
循環(huán)之中。具體來(lái)說(shuō),如果一個(gè)屬性的enumerable
為false
,下面三個(gè)操作不會(huì)取到該屬性。
for..in
循環(huán)Object.keys
方法JSON.stringify
方法因此,enumerable
可以用來(lái)設(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
屬性的enumerable
為false
,所以一般的遍歷操作都無(wú)法獲取該屬性,使得它有點(diǎn)像“秘密”屬性,但不是真正的私有屬性,還是可以直接獲取它的值。
基本上,JavaScript原生提供的屬性都是不可枚舉的,用戶自定義的屬性都是可枚舉的。
與枚舉性相關(guān)的幾個(gè)操作的區(qū)別的是,for...in
循環(huán)包括繼承自原型對(duì)象的屬性,Object.keys
方法只返回對(duì)象本身的屬性。如果需要獲取對(duì)象自身的所有屬性,不管是否可枚舉,可以使用Object.getOwnPropertyNames
方法,詳見下文。
考慮到JSON.stringify
方法會(huì)排除enumerable
為false
的值,有時(shí)可以利用這一點(diǎn),為對(duì)象添加注釋信息。
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
對(duì)象作為注釋部分,加入car
對(duì)象。由于ownerInfo
屬性不可枚舉,所以JSON.stringify
方法最后輸出car
對(duì)象時(shí),會(huì)忽略ownerInfo
屬性。
這提示我們,如果你不愿意某些屬性出現(xiàn)在JSON輸出之中,可以把它的enumerable
屬性設(shè)為false
。
可配置性(configurable)決定了是否可以修改屬性描述對(duì)象。也就是說(shuō),當(dāng)configurable
為false
的時(shí)候,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
上面代碼首先定義對(duì)象o
,并且定義o
的屬性p
的configurable
為false
。然后,逐一改動(dòng)value
、writable
、enumerable
、configurable
,結(jié)果都報(bào)錯(cuò)。
需要注意的是,writable
只有在從false
改為true
會(huì)報(bào)錯(cuò),從true
改為false
則是允許的。
var o = Object.defineProperty({}, 'p', {
writable: true,
configurable: false
});
Object.defineProperty(o,'p', {writable: false})
// 修改成功
至于value
,只要writable
和configurable
有一個(gè)為true
,就允許改動(dòng)。
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})
// 修改成功
另外,configurable
為false
時(shí),直接對(duì)該屬性賦值,不報(bào)錯(cuò),但不會(huì)成功。
var o = Object.defineProperty({}, 'p', {
value: 1,
configurable: false
});
o.p = 2;
o.p // 1
上面代碼中,o
對(duì)象的p
屬性是不可配置的,對(duì)它賦值是不會(huì)生效的。
可配置性決定了一個(gè)變量是否可以被刪除(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
上面代碼中的對(duì)象o
有兩個(gè)屬性,p1
是可配置的,p2
是不可配置的。結(jié)果,p2
就無(wú)法刪除。
需要注意的是,當(dāng)使用var
命令聲明變量時(shí),變量的configurable
為false
。
var a1 = 1;
Object.getOwnPropertyDescriptor(this,'a1')
// Object {
// value: 1,
// writable: true,
// enumerable: true,
// configurable: false
// }
而不使用var
命令聲明變量時(shí)(或者使用屬性賦值的方式聲明變量),變量的可配置性為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 = 1
與a3 = 1
是等價(jià)的寫法。window
指的是瀏覽器的頂層對(duì)象。
這種差異意味著,如果一個(gè)變量是使用var
命令生成的,就無(wú)法用delete
命令刪除。也就是說(shuō),delete
只能刪除對(duì)象的屬性。
var a1 = 1;
a2 = 1;
delete a1 // false
delete a2 // true
a1 // 1
a2 // ReferenceError: a2 is not defined
可寫性(writable)決定了屬性的值(value)是否可以被改變。
var o = {};
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
o.a // 37
o.a = 25;
o.a // 37
上面代碼將o
對(duì)象的a
屬性可寫性設(shè)為false
,然后改變這個(gè)屬性的值,就不會(huì)有任何效果。
注意,正常模式下,對(duì)可寫性為false
的屬性賦值不會(huì)報(bào)錯(cuò),只會(huì)默默失敗。但是,嚴(yán)格模式下會(huì)報(bào)錯(cuò),即使是對(duì)a
屬性重新賦予一個(gè)同樣的值。
關(guān)于可寫性,還有一種特殊情況。就是如果原型對(duì)象的某個(gè)屬性的可寫性為false
,那么派生對(duì)象將無(wú)法自定義這個(gè)屬性。
var proto = Object.defineProperty({}, 'foo', {
value: 'a',
writable: false
});
var o = Object.create(proto);
o.foo = 'b';
o.foo // 'a'
上面代碼中,對(duì)象proto
的foo
屬性不可寫,結(jié)果proto
的派生對(duì)象o
,也不可以再自定義這個(gè)屬性了。在嚴(yán)格模式下,這樣做還會(huì)拋出一個(gè)錯(cuò)誤。但是,有一個(gè)規(guī)避方法,就是通過(guò)覆蓋屬性描述對(duì)象,繞過(guò)這個(gè)限制,原因是這種情況下,原型鏈會(huì)被完全忽視。
Object.defineProperty(o, 'foo', {
value: 'b'
});
o.foo // 'b'
Object.getOwnPropertyNames
方法返回直接定義在某個(gè)對(duì)象上面的全部屬性的名稱,而不管該屬性是否可枚舉。
var o = Object.defineProperties({}, {
p1: { value: 1, enumerable: true },
p2: { value: 2, enumerable: false }
});
Object.getOwnPropertyNames(o)
// ["p1", "p2"]
一般來(lái)說(shuō),系統(tǒng)原生的屬性(即非用戶自定義的屬性)都是不可枚舉的。
// 比如,數(shù)組實(shí)例自帶length屬性是不可枚舉的
Object.keys([]) // []
Object.getOwnPropertyNames([]) // [ 'length' ]
// Object.prototype對(duì)象的自帶屬性也都是不可枚舉的
Object.keys(Object.prototype) // []
Object.getOwnPropertyNames(Object.prototype)
// ['hasOwnProperty',
// 'valueOf',
// 'constructor',
// 'toLocaleString',
// 'isPrototypeOf',
// 'propertyIsEnumerable',
// 'toString']
上面代碼可以看到,數(shù)組的實(shí)例對(duì)象([]
)沒(méi)有可枚舉屬性,不可枚舉屬性有l(wèi)ength;Object.prototype對(duì)象也沒(méi)有可枚舉屬性,但是有不少不可枚舉屬性。
對(duì)象實(shí)例的propertyIsEnumerable
方法用來(lái)判斷一個(gè)屬性是否可枚舉。
var o = {};
o.p = 123;
o.propertyIsEnumerable('p') // true
o.propertyIsEnumerable('toString') // false
上面代碼中,用戶自定義的p
屬性是可枚舉的,而繼承自原型對(duì)象的toString
屬性是不可枚舉的。
除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函數(shù)稱為setter
,使用set
命令;取值函數(shù)稱為getter
,使用get
命令。
存取器提供的是虛擬屬性,即該屬性的值不是實(shí)際存在的,而是每次讀取時(shí)計(jì)算生成的。利用這個(gè)功能,可以實(shí)現(xiàn)許多高級(jí)特性,比如每個(gè)屬性禁止賦值。
var o = {
get p() {
return 'getter';
},
set p(value) {
console.log('setter: ' + value);
}
};
上面代碼中,o
對(duì)象內(nèi)部的get
和set
命令,分別定義了p
屬性的取值函數(shù)和存值函數(shù)。定義了這兩個(gè)函數(shù)之后,對(duì)p
屬性取值時(shí),取值函數(shù)會(huì)自動(dòng)調(diào)用;對(duì)p
屬性賦值時(shí),存值函數(shù)會(huì)自動(dòng)調(diào)用。
o.p // "getter"
o.p = 123 // "setter: 123"
注意,取值函數(shù)Getter不能接受參數(shù),存值函數(shù)Setter只能接受一個(gè)參數(shù)(即屬性的值)。另外,對(duì)象也不能有與取值函數(shù)同名的屬性。比如,上面的對(duì)象o
設(shè)置了取值函數(shù)p
以后,就不能再另外定義一個(gè)p
屬性。
存取器往往用于,屬性的值需要依賴對(duì)象內(nèi)部數(shù)據(jù)的場(chǎng)合。
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ù),都依賴于對(duì)內(nèi)部屬性$n
的操作。
存取器也可以通過(guò)Object.defineProperty
定義。
var d = new Date();
Object.defineProperty(d, 'month', {
get: function () {
return d.getMonth();
},
set: function (v) {
d.setMonth(v);
}
});
上面代碼為Date
的實(shí)例對(duì)象d
,定義了一個(gè)可讀寫的month
屬性。
存取器也可以使用Object.create
方法定義。
var o = Object.create(Object.prototype, {
foo: {
get: function () {
return 'getter';
},
set: function (value) {
console.log('setter: '+value);
}
}
});
如果使用上面這種寫法,屬性foo
必須定義一個(gè)屬性描述對(duì)象。該對(duì)象的get
和set
屬性,分別是foo
的取值函數(shù)和存值函數(shù)。
利用存取器,可以實(shí)現(xiàn)數(shù)據(jù)對(duì)象與DOM對(duì)象的雙向綁定。
Object.defineProperty(user, 'name', {
get: function () {
return document.getElementById('foo').value;
},
set: function (newValue) {
document.getElementById('foo').value = newValue;
},
configurable: true
});
上面代碼使用存取函數(shù),將DOM對(duì)象foo
與數(shù)據(jù)對(duì)象user
的name
屬性,實(shí)現(xiàn)了綁定。兩者之中只要有一個(gè)對(duì)象發(fā)生變化,就能在另一個(gè)對(duì)象上實(shí)時(shí)反映出來(lái)。
有時(shí),我們需要將一個(gè)對(duì)象的所有屬性,拷貝到另一個(gè)對(duì)象。ES5沒(méi)有提供這個(gè)方法,必須自己實(shí)現(xiàn)。
var extend = function (to, from) {
for (var property in from) {
to[property] = from[property];
}
return to;
}
extend({}, {
a: 1
})
// {a: 1}
上面這個(gè)方法的問(wèn)題在于,如果遇到存取器定義的屬性,會(huì)只拷貝值。
extend({}, {
get a() { return 1 }
})
// {a: 1}
為了解決這個(gè)問(wèn)題,我們可以通過(guò)Object.defineProperty
方法來(lái)拷貝屬性。
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 } })
這段代碼還是有問(wèn)題,拷貝某些屬性時(shí)會(huì)失效。
extend(document.body.style, {
backgroundColor: "red"
});
上面代碼的目的是,設(shè)置document.body.style.backgroundColor
屬性為red
,但是實(shí)際上網(wǎng)頁(yè)的背景色并不會(huì)變紅。但是,如果用第一種簡(jiǎn)單拷貝的方法,反而能夠達(dá)到目的。這提示我們,可以把兩種方法結(jié)合起來(lái),對(duì)于簡(jiǎn)單屬性,就直接拷貝,對(duì)于那些通過(guò)屬性描述對(duì)象設(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];
}
}
}
上面的這段代碼,可以很好地拷貝對(duì)象所有可遍歷(enumerable)的屬性。
JavaScript提供了三種方法,精確控制一個(gè)對(duì)象的讀寫狀態(tài),防止對(duì)象被改變。最弱一層的保護(hù)是Object.preventExtensions
,其次是Object.seal
,最強(qiáng)的Object.freeze
。
Object.preventExtensions
方法可以使得一個(gè)對(duì)象無(wú)法再添加新的屬性。
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)格模式下,則會(huì)拋出一個(gè)錯(cuò)誤。
(function () {
'use strict';
o.p = '1'
}());
// TypeError: Can't add property bar, object is not extensible
不過(guò),對(duì)于使用了preventExtensions
方法的對(duì)象,可以用delete
命令刪除它的現(xiàn)有屬性。
var o = new Object();
o.p = 1;
Object.preventExtensions(o);
delete o.p;
o.p // undefined
Object.isExtensible
方法用于檢查一個(gè)對(duì)象是否使用了Object.preventExtensions
方法。也就是說(shuō),檢查是否可以為一個(gè)對(duì)象添加屬性。
var o = new Object();
Object.isExtensible(o) // true
Object.preventExtensions(o);
Object.isExtensible(o) // false
上面代碼新生成了一個(gè)o對(duì)象,對(duì)該對(duì)象使用Object.isExtensible
方法,返回true
,表示可以添加新屬性。對(duì)該對(duì)象使用Object.preventExtensions
方法以后,再使用Object.isExtensible
方法,返回false
,表示已經(jīng)不能添加新屬性了。
Object.seal
方法使得一個(gè)對(duì)象既無(wú)法添加新屬性,也無(wú)法刪除舊屬性。
var o = {
p: 'hello'
};
Object.seal(o);
delete o.p;
o.p // "hello"
o.x = 'world';
o.x // undefined
上面代碼中,一個(gè)對(duì)象執(zhí)行Object.seal
方法以后,就無(wú)法添加新屬性和刪除舊屬性了。
Object.seal
實(shí)質(zhì)是把屬性描述對(duì)象的configurable
屬性設(shè)為false
,因此屬性描述對(duì)象不再能改變了。
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
方法之后,屬性描述對(duì)象的configurable
屬性就變成了false
,然后改變enumerable
屬性就會(huì)報(bào)錯(cuò)。
可寫性(writable)有點(diǎn)特別。如果writable
為false
,使用Object.seal
方法以后,將無(wú)法將其變成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
,改變這個(gè)設(shè)置將報(bào)錯(cuò);如果原為true
,則不會(huì)有問(wèn)題。
至于屬性對(duì)象的value
是否可改變,是由writable
決定的。
var o = { p: 'a' };
Object.seal(o);
o.p = 'b';
o.p // 'b'
上面代碼中,Object.seal
方法對(duì)p
屬性的value
無(wú)效,是因?yàn)榇藭r(shí)p
屬性的writable
為true
。
Object.isSealed
方法用于檢查一個(gè)對(duì)象是否使用了Object.seal
方法。
var o = { p: 'a' };
Object.seal(o);
Object.isSealed(o) // true
這時(shí),Object.isExtensible
方法也返回false
。
var o = { p: 'a' };
Object.seal(o);
Object.isExtensible(o) // false
Object.freeze
方法可以使得一個(gè)對(duì)象無(wú)法添加新屬性、無(wú)法刪除舊屬性、也無(wú)法改變屬性的值,使得這個(gè)對(duì)象實(shí)際上變成了常量。
var o = {
p: 'hello'
};
Object.freeze(o);
o.p = 'world';
o.p // hello
o.t = 'hello';
o.t // undefined
上面代碼中,對(duì)現(xiàn)有屬性重新賦值(o.p = 'world'
)或者添加一個(gè)新屬性,并不會(huì)報(bào)錯(cuò),只是默默地失敗。但是,如果是在嚴(yán)格模式下,就會(huì)報(bào)錯(cuò)。
var o = {
p: 'hello'
};
Object.freeze(o);
// 對(duì)現(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
方法用于檢查一個(gè)對(duì)象是否使用了Object.freeze()
方法。
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isFrozen(obj) // true
前面說(shuō)過(guò),如果一個(gè)對(duì)象被凍結(jié),再對(duì)它的屬性賦值,在嚴(yán)格模式下會(huì)報(bào)錯(cuò)。Object.isFrozen
方法可以防止發(fā)生這樣的錯(cuò)誤。
var obj = {
p: 'hello'
};
Object.freeze(obj);
if (!Object.isFrozen(obj)) {
obj.p = 'world';
}
上面代碼中,確認(rèn)obj
沒(méi)有被凍結(jié)后,再對(duì)它的屬性賦值,就不會(huì)報(bào)錯(cuò)了。
需要注意的是,使用上面這些方法鎖定對(duì)象的可寫性,但是依然可以通過(guò)改變?cè)搶?duì)象的原型對(duì)象,來(lái)為它增加屬性。
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
另外一個(gè)局限是,如果屬性值是對(duì)象,上面這些方法只能凍結(jié)屬性指向的對(duì)象,而不能凍結(jié)對(duì)象本身的內(nèi)容。
var obj = {
foo: 1,
bar: ['a', 'b']
};
Object.freeze(obj);
obj.bar.push('c');
obj.bar // ["a", "b", "c"]
上面代碼中,obj.bar
屬性指向一個(gè)數(shù)組,obj
對(duì)象被凍結(jié)以后,這個(gè)指向無(wú)法改變,即無(wú)法指向其他值,但是所指向的數(shù)組是可以改變的。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: