你必須了解的JavaScript中的屬性描述對象詳解(下)
元屬性
value
value
屬性是目標屬性的值。
var obj = {}; obj.p = 123; Object.getOwnPropertyDescriptor(obj,'p').value // 123 Object.defineProperty(obj,'p',{value:246}); obj.p //246
上面代碼是通過value
屬性,讀取或改寫obj.p
的例子。
writable
writable
屬性是一個布爾值,決定了目標屬性的值(value)是否可以被改變。
var obj = {}; Object.defineProperty(obj, 'a',{ value:37, writable:false }); obj.a //37 obj.a = 25; obj.a //37
上面代碼中,obj.a
的writable
屬性是false
。然后,改變obj.a
的值,不會有任何效果。
注意,正常模式下,對writable
為false
的屬性賦值不會報錯,只會默默失敗。但是,嚴格模式下會報錯,即使對a
屬性重新賦予一個同樣的值。
// ‘use strict' var obj = {}; Object.defineProperty(obj,'a',{ value:37, writable:false }); obj.a = 37; // Uncaught TypeError: Cannot assign to read only property 'a' of object
上面代碼是嚴格模式,對obj.a
任何賦值行為都會報錯。
如果原型對象的某個屬性的writable
為false
,那么子對象將無法自定義這個屬性。
var proto = Object.defineProperty({},'foo',{ value:'a', writable:false }); var obj = Object.create(proto); obj.foo = 'b'; obj.foo //'a'
上面代碼中,proto
是原型對象,它的foo
屬性不可寫。obj
對象繼承proto
,也不可以再自定義這個屬性了。如果是嚴格模式,這樣做還會拋出一個錯誤。
但是,有一個規(guī)避方法,就是通過覆蓋屬性描述對象,繞過這個限制。原因是這種情況下,原型鏈會被完全忽視。
var proto = Object.defineProperty({},'foo',{ value:'a', writable:false }); var obj = Object.create(proto); Object.defineProperty(obj,'foo',{ value:'b' }); obj.foo //"b"
enumerable
enumerable
(可遍歷性)返回一個布爾值,表示目標屬性是否可遍歷。
JavaScript 的早期版本,for...in
循環(huán)是基于in
運算符的。我們知道,in
運算符不管某個屬性是對象自身的還是繼承的,都會返回true
。
var obj = {}; 'totring' in obj //true
上面代碼中,toString
不是obj
對象自身的屬性,但是in
運算符也返回true
,這導致了toString
屬性也會被for...in
循環(huán)遍歷。
這顯然不太合理,后來就引入了“可遍歷性”這個概念。只有可遍歷的屬性,才會被for...in
循環(huán)遍歷,同時還規(guī)定toString
這一類實例對象繼承的原生屬性,都是不可遍歷的,這樣就保證了for...in
循環(huán)的可用性。
具體來說,如果一個屬性的enumerable
為false
,下面三個操作不會取到該屬性。
for..in
循環(huán)Object.keys
方法JSON.stringify
方法
因此,enumerable
可以用來設置“秘密”屬性。
var obj = {}; Object.defineProperty(obj,'x',{ value:123; enumerable:false }); obj.x //123 for(var key in obj){ console.log(key); } //undefined Object.keys(obj) //[] JSON.stringify(obj) //"{}"
上面代碼中,obj.x
屬性的enumerable
為false
,所以一般的遍歷操作都無法獲取該屬性,使得它有點像“秘密”屬性,但不是真正的私有屬性,還是可以直接獲取它的值。
注意,for...in
循環(huán)包括繼承的屬性,Object.keys
方法不包括繼承的屬性。如果需要獲取對象自身的所有屬性,不管是否可遍歷,可以使用Object.getOwnPropertyNames
方法。
另外,JSON.stringify
方法會排除enumerable
為false
的屬性,有時可以利用這一點。如果對象的 JSON 格式輸出要排除某些屬性,就可以把這些屬性的enumerable
設為false
。
configurable
configurable
(可配置性)返回一個布爾值,決定了是否可以修改屬性描述對象。也就是說,configurable
為false
時,value
、writable
、enumerable
和configurable
都不能被修改了。
var obj = Object.defineProperty({},'p',{ value:1, writable:false, enumerable:false, configurable:false }); Object.defineProperty(obj,'p',{value:2}) //TypeError:Cannot redefine property:p Object.defineProperty(obj,'p',{writable:true}) //TypeError:Cannot redefine property:p Object.definetyProperty(obj,'p',{enumerable:true}) //TypeError:Cannot redefine property:p Object.defineProperty(obj,'p',{configurable:true}) //TypeError:Cannot redefine property:p
上面代碼中,obj.p
的configurable
為false
。然后,改動value
、writable
、enumerable
、configurable
,結果都報錯。
注意,writable
只有在false
改為true
會報錯,true
改為false
是允許的。
var obj = Object.defineProperty({},'p',{ writable:true, configurable:false }); Object.defineProperty(obj,'p',{writable:false}) //修改成功
至于value
,只要writable
和configurable
有一個為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}) //修改成功
另外,writable
為false
時,直接目標屬性賦值,不報錯,但不會成功。
var obj = Object.defineProperty({},'p',{ value:1, writable:false, configurable:false }); obj.p = 2; obj.p //1
上面代碼中,obj.p
的writable
為false
,對obj.p
直接賦值不會生效。如果是嚴格模式,還會報錯。
可配置性決定了目標屬性是否可以被刪除(delete)。
var obj = Object.defineProperties({},{ p1:{ value:1,configurable:true}, p2:{ value:2,configurable:false} }); delete obj.p1 // true delete obj.p2 // false obj.p1 //undefined obj.p2 //2
上面代碼中,obj.p1
的configurable
是true
,所以可以被刪除,obj.p2
就無法刪除。
存取器
除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函數(shù)稱為setter
,使用屬性描述對象的set
屬性;取值函數(shù)稱為getter
,使用屬性描述對象的get
屬性。
一旦對目標屬性定義了存取器,那么存取的時候,都將執(zhí)行對應的函數(shù)。利用這個功能,可以實現(xiàn)許多高級特性,比如定制屬性的讀取和賦值行為。
var obj = Object.defineProperty({},'p',{ get:function(){ return 'getter'; }, set:function(value){ console.log('setter:' + value); } }); obj.p //"getter" obj.p = 123 //"setter: 123"
上面代碼中,obj.p
定義了get
和set
屬性。obj.p
取值時,就會調用get
;賦值時,就會調用set
。
JavaScript 還提供了存取器的另一種寫法。
// 寫法二 var obj = { get p() { return 'getter'; }, set p(value) { console.log('setter: ' + value); } };
上面兩種寫法,雖然屬性p
的讀取和賦值行為是一樣的,但是有一些細微的區(qū)別。第一種寫法,屬性p
的configurable
和enumerable
都為false
,從而導致屬性p
是不可遍歷的;第二種寫法,屬性p
的configurable
和enumerable
都為true
,因此屬性p
是可遍歷的。實際開發(fā)中,寫法二更常用。
注意,取值函數(shù)get
不能接受參數(shù),存值函數(shù)set
只能接受一個參數(shù)(即屬性的值)。
存取器往往用于,屬性的值依賴對象內部數(shù)據(jù)的場合。
var obj ={ $n : 5, get next(){ return this.$n++ }, set next(n){ if(n >= this.$n)this.$n = n; else throw new Error('新的值必須大于當前值‘); } }; obj.next //5 obj.next = 10; obj.next //10 obj.next = 5; //Uncaught Error:新的值必須大于當前值
上面代碼中,next
屬性的存值函數(shù)和取值函數(shù),都依賴于內部屬性$n
。
對象的拷貝
有時,我們需要將一個對象的所有屬性,拷貝到另一個對象,可以用下面的方法實現(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) { if (!from.hasOwnProperty(property)) continue; Object.defineProperty( to, property, Object.getOwnPropertyDescriptor(from, property) ); } return to; } extend({}, { get a(){ return 1 } }) // { get a(){ return 1 } })
上面代碼中,hasOwnProperty
那一行用來過濾掉繼承的屬性,否則可能會報錯,因為Object.getOwnPropertyDescriptor
讀不到繼承屬性的屬性描述對象。
控制對象狀態(tài)
有時需要凍結對象的讀寫狀態(tài),防止對象被改變。JavaScript 提供了三種凍結方法,最弱的一種是Object.preventExtensions
,其次是Object.seal
,最強的是Object.freeze
。
Object.preventExtensions()
Object.preventExtensions
方法可以使得一個對象無法再添加新的屬性。
var obj = new Object(); Object.preventExtensions(obj); Object.defineProperty(obj,'p',{ value:'hello' }); // TypeError:Cannot define property:p,object is not extensible. obj.p = 1; obj.p .. undefined
上面代碼中,obj
對象經(jīng)過Object.preventExtensions
以后,就無法添加新屬性了。
Object.isExtensible()
Object.isExtensible
方法用于檢查一個對象是否使用了Object.preventExtensions
方法。也就是說,檢查是否可以為一個對象添加屬性。
var obj = new Object(); Object.isExtensible(obj) // true Object.preventExtensions(obj); Object.isExtensible(obj) // false
上面代碼中,對obj
對象使用Object.preventExtensions
方法以后,再使用Object.isExtensible
方法,返回false
,表示已經(jīng)不能添加新屬性了。
Object.seal()
Object.seal
方法使得一個對象既無法添加新屬性,也無法刪除舊屬性。
var obj = { p:'hello' }; Object.seal(obj); delete obj.p; obj.p //"hello" obj.x = 'world'; obj.x // undefined
上面代碼中,obj
對象執(zhí)行Object.seal
方法以后,就無法添加新屬性和刪除舊屬性了。
Object.seal
實質是把屬性描述對象的configurable
屬性設為false
,因此屬性描述對象不再能改變了。
var obj = { p: 'a' }; // seal方法之前 Object.getOwnPropertyDescriptor(obj, 'p') // Object { // value: "a", // writable: true, // enumerable: true, // configurable: true // } Object.seal(obj); // seal方法之后 Object.getOwnPropertyDescriptor(obj, '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
屬性就會報錯。
Object.seal
只是禁止新增或刪除屬性,并不影響修改某個屬性的值。
var obj = {p:'a'}; Object.seal(obj); obj.p = 'b'; obj.p //'b'
上面代碼中,Object.seal
方法對p
屬性的value
無效,是因為此時p
屬性的可寫性由writable
決定。
Object.isSealed()
Object.isSealed
方法用于檢查一個對象是否使用了Object.seal
方法。
var obj = { p: 'a' }; Object.seal(obj); Object.isSealed(obj) // true
這時,Object.isExtensible
方法也返回false
。
var obj = { p: 'a' }; Object.seal(obj); Object.isExtensible(obj) // false
Object.freeze()
Object.freeze
方法可以使得一個對象無法添加新屬性、無法刪除舊屬性、也無法改變屬性的值,使得這個對象實際上變成了常量。
var obj = { p: 'hello' }; Object.freeze(obj); obj.p = 'world'; obj.p // "hello" obj.t = 'hello'; obj.t // undefined delete obj.p // false obj.p // "hello"
上面代碼中,對obj
對象進行Object.freeze()
以后,修改屬性、新增屬性、刪除屬性都無效了。這些操作并不報錯,只是默默地失敗。如果在嚴格模式下,則會報錯。
Object.isFrozen()
Object.isFrozen
方法用于檢查一個對象是否使用了Object.freeze
方法。
var obj = { p: 'hello' }; Object.freeze(obj); Object.isFrozen(obj) // true
使用Object.freeze
方法以后,Object.isSealed
將會返回true
,Object.isExtensible
返回false
。
var obj = { p: 'hello' }; Object.freeze(obj); Object.isSealed(obj) // true Object.isExtensible(obj) // false
Object.isFrozen
的一個用途是,確認某個對象沒有被凍結后,再對它的屬性賦值。
var obj = { p: 'hello' }; Object.freeze(obj); if (!Object.isFrozen(obj)) { obj.p = 'world'; }
上面代碼中,確認obj
沒有被凍結后,再對它的屬性賦值,就不會報錯了。
局限性
上面的三個方法鎖定對象的可寫性有一個漏洞:可以通過改變原型對象,來為對象增加屬性。
var obj = new Object(); Object.preventEctensions(obj); var proto = Object.getPrototypeOf(obj); proto.t = 'hello'; obj.t //hello
上面代碼中,對象obj
本身不能新增屬性,但是可以在它的原型對象上新增屬性,就依然能夠在obj
上讀到。
一種解決方案是,把obj
的原型也凍結住。
var obj = new Object(); Object.preventExtensions(obj); var proto = Object.getPrototypeOf(obj); Object.preventExtensions(proto); proto.t = 'hello'; obj.t //undefined
另外一個局限是,如果屬性值是對象,上面這些方法只能凍結屬性指向的對象,而不能凍結對象本身的內容。
var obj = { foo:1, bar:['a','b'] }; Object.freeze(obj); obj.bar.push('c'); obj.bar // ["a',"b","c"]
上面代碼中,obj.bar
屬性指向一個數(shù)組,obj
對象被凍結以后,這個指向無法改變,即無法指向其他值,但是所指向的數(shù)組是可以改變的。
以上就是你必須了解的JavaScript中的屬性描述對象詳解(下)的詳細內容,更多關于JavaScript屬性描述對象的資料請關注腳本之家其它相關文章!
相關文章
Javascript的構造函數(shù)和constructor屬性
我們知道,默認情況下,對一個函數(shù)前面使用new,可以構造出一個對象。每一個對象都有一個constructor屬性,這個constructor屬性指向構造出該對象的函數(shù)。2010-01-01JS解決?Array.fill()參數(shù)為對象指向同一個引用地址的問題
這篇文章主要介紹了JS解決?Array.fill()參數(shù)為對象指向同一個引用地址問題,解決方案使用map返回出不同的引用的地址,fill參數(shù)可隨意填寫(不為空),主要是map函數(shù)中返回的數(shù)據(jù),需要的朋友可以參考下2023-02-02