你必須了解的JavaScript中的屬性描述對(duì)象詳解(下)
元屬性
value
value屬性是目標(biāo)屬性的值。
var obj = {};
obj.p = 123;
Object.getOwnPropertyDescriptor(obj,'p').value
// 123
Object.defineProperty(obj,'p',{value:246});
obj.p //246
上面代碼是通過(guò)value屬性,讀取或改寫obj.p的例子。
writable
writable屬性是一個(gè)布爾值,決定了目標(biāo)屬性的值(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的值,不會(huì)有任何效果。
注意,正常模式下,對(duì)writable為false的屬性賦值不會(huì)報(bào)錯(cuò),只會(huì)默默失敗。但是,嚴(yán)格模式下會(huì)報(bào)錯(cuò),即使對(duì)a屬性重新賦予一個(gè)同樣的值。
// ‘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
上面代碼是嚴(yán)格模式,對(duì)obj.a任何賦值行為都會(huì)報(bào)錯(cuò)。
如果原型對(duì)象的某個(gè)屬性的writable為false,那么子對(duì)象將無(wú)法自定義這個(gè)屬性。
var proto = Object.defineProperty({},'foo',{
value:'a',
writable:false
});
var obj = Object.create(proto);
obj.foo = 'b';
obj.foo //'a'
上面代碼中,proto是原型對(duì)象,它的foo屬性不可寫。obj對(duì)象繼承proto,也不可以再自定義這個(gè)屬性了。如果是嚴(yán)格模式,這樣做還會(huì)拋出一個(gè)錯(cuò)誤。
但是,有一個(gè)規(guī)避方法,就是通過(guò)覆蓋屬性描述對(duì)象,繞過(guò)這個(gè)限制。原因是這種情況下,原型鏈會(huì)被完全忽視。
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(可遍歷性)返回一個(gè)布爾值,表示目標(biāo)屬性是否可遍歷。
JavaScript 的早期版本,for...in循環(huán)是基于in運(yùn)算符的。我們知道,in運(yùn)算符不管某個(gè)屬性是對(duì)象自身的還是繼承的,都會(huì)返回true。
var obj = {};
'totring' in obj //true
上面代碼中,toString不是obj對(duì)象自身的屬性,但是in運(yùn)算符也返回true,這導(dǎo)致了toString屬性也會(huì)被for...in循環(huán)遍歷。
這顯然不太合理,后來(lái)就引入了“可遍歷性”這個(gè)概念。只有可遍歷的屬性,才會(huì)被for...in循環(huán)遍歷,同時(shí)還規(guī)定toString這一類實(shí)例對(duì)象繼承的原生屬性,都是不可遍歷的,這樣就保證了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 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,所以一般的遍歷操作都無(wú)法獲取該屬性,使得它有點(diǎn)像“秘密”屬性,但不是真正的私有屬性,還是可以直接獲取它的值。
注意,for...in循環(huán)包括繼承的屬性,Object.keys方法不包括繼承的屬性。如果需要獲取對(duì)象自身的所有屬性,不管是否可遍歷,可以使用Object.getOwnPropertyNames方法。
另外,JSON.stringify方法會(huì)排除enumerable為false的屬性,有時(shí)可以利用這一點(diǎn)。如果對(duì)象的 JSON 格式輸出要排除某些屬性,就可以把這些屬性的enumerable設(shè)為false。
configurable
configurable(可配置性)返回一個(gè)布爾值,決定了是否可以修改屬性描述對(duì)象。也就是說(shuō),configurable為false時(shí),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。然后,改動(dòng)value、writable、enumerable、configurable,結(jié)果都報(bào)錯(cuò)。
注意,writable只有在false改為true會(huì)報(bào)錯(cuò),true改為false是允許的。
var obj = Object.defineProperty({},'p',{
writable:true,
configurable:false
});
Object.defineProperty(obj,'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})
//修改成功
另外,writable為false時(shí),直接目標(biāo)屬性賦值,不報(bào)錯(cuò),但不會(huì)成功。
var obj = Object.defineProperty({},'p',{
value:1,
writable:false,
configurable:false
});
obj.p = 2;
obj.p //1
上面代碼中,obj.p的writable為false,對(duì)obj.p直接賦值不會(huì)生效。如果是嚴(yán)格模式,還會(huì)報(bào)錯(cuò)。
可配置性決定了目標(biāo)屬性是否可以被刪除(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就無(wú)法刪除。
存取器
除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函數(shù)稱為setter,使用屬性描述對(duì)象的set屬性;取值函數(shù)稱為getter,使用屬性描述對(duì)象的get屬性。
一旦對(duì)目標(biāo)屬性定義了存取器,那么存取的時(shí)候,都將執(zhí)行對(duì)應(yīng)的函數(shù)。利用這個(gè)功能,可以實(shí)現(xiàn)許多高級(jí)特性,比如定制屬性的讀取和賦值行為。
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取值時(shí),就會(huì)調(diào)用get;賦值時(shí),就會(huì)調(diào)用set。
JavaScript 還提供了存取器的另一種寫法。
// 寫法二
var obj = {
get p() {
return 'getter';
},
set p(value) {
console.log('setter: ' + value);
}
};
上面兩種寫法,雖然屬性p的讀取和賦值行為是一樣的,但是有一些細(xì)微的區(qū)別。第一種寫法,屬性p的configurable和enumerable都為false,從而導(dǎo)致屬性p是不可遍歷的;第二種寫法,屬性p的configurable和enumerable都為true,因此屬性p是可遍歷的。實(shí)際開發(fā)中,寫法二更常用。
注意,取值函數(shù)get不能接受參數(shù),存值函數(shù)set只能接受一個(gè)參數(shù)(即屬性的值)。
存取器往往用于,屬性的值依賴對(duì)象內(nèi)部數(shù)據(jù)的場(chǎng)合。
var obj ={
$n : 5,
get next(){ return this.$n++ },
set next(n){
if(n >= this.$n)this.$n = n;
else throw new Error('新的值必須大于當(dāng)前值‘);
}
};
obj.next //5
obj.next = 10;
obj.next //10
obj.next = 5;
//Uncaught Error:新的值必須大于當(dāng)前值
上面代碼中,next屬性的存值函數(shù)和取值函數(shù),都依賴于內(nèi)部屬性$n。
對(duì)象的拷貝
有時(shí),我們需要將一個(gè)對(duì)象的所有屬性,拷貝到另一個(gè)對(duì)象,可以用下面的方法實(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) {
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那一行用來(lái)過(guò)濾掉繼承的屬性,否則可能會(huì)報(bào)錯(cuò),因?yàn)?code>Object.getOwnPropertyDescriptor讀不到繼承屬性的屬性描述對(duì)象。
控制對(duì)象狀態(tài)
有時(shí)需要凍結(jié)對(duì)象的讀寫狀態(tài),防止對(duì)象被改變。JavaScript 提供了三種凍結(jié)方法,最弱的一種是Object.preventExtensions,其次是Object.seal,最強(qiáng)的是Object.freeze。
Object.preventExtensions()
Object.preventExtensions方法可以使得一個(gè)對(duì)象無(wú)法再添加新的屬性。
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對(duì)象經(jīng)過(guò)Object.preventExtensions以后,就無(wú)法添加新屬性了。
Object.isExtensible()
Object.isExtensible方法用于檢查一個(gè)對(duì)象是否使用了Object.preventExtensions方法。也就是說(shuō),檢查是否可以為一個(gè)對(duì)象添加屬性。
var obj = new Object(); Object.isExtensible(obj) // true Object.preventExtensions(obj); Object.isExtensible(obj) // false
上面代碼中,對(duì)obj對(duì)象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已經(jīng)不能添加新屬性了。
Object.seal()
Object.seal方法使得一個(gè)對(duì)象既無(wú)法添加新屬性,也無(wú)法刪除舊屬性。
var obj = { p:'hello' };
Object.seal(obj);
delete obj.p;
obj.p //"hello"
obj.x = 'world';
obj.x // undefined
上面代碼中,obj對(duì)象執(zhí)行Object.seal方法以后,就無(wú)法添加新屬性和刪除舊屬性了。
Object.seal實(shí)質(zhì)是把屬性描述對(duì)象的configurable屬性設(shè)為false,因此屬性描述對(duì)象不再能改變了。
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方法之后,屬性描述對(duì)象的configurable屬性就變成了false,然后改變enumerable屬性就會(huì)報(bào)錯(cuò)。
Object.seal只是禁止新增或刪除屬性,并不影響修改某個(gè)屬性的值。
var obj = {p:'a'};
Object.seal(obj);
obj.p = 'b';
obj.p //'b'
上面代碼中,Object.seal方法對(duì)p屬性的value無(wú)效,是因?yàn)榇藭r(shí)p屬性的可寫性由writable決定。
Object.isSealed()
Object.isSealed方法用于檢查一個(gè)對(duì)象是否使用了Object.seal方法。
var obj = { p: 'a' };
Object.seal(obj);
Object.isSealed(obj) // true
這時(shí),Object.isExtensible方法也返回false。
var obj = { p: 'a' };
Object.seal(obj);
Object.isExtensible(obj) // false
Object.freeze()
Object.freeze方法可以使得一個(gè)對(duì)象無(wú)法添加新屬性、無(wú)法刪除舊屬性、也無(wú)法改變屬性的值,使得這個(gè)對(duì)象實(shí)際上變成了常量。
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"
上面代碼中,對(duì)obj對(duì)象進(jìn)行Object.freeze()以后,修改屬性、新增屬性、刪除屬性都無(wú)效了。這些操作并不報(bào)錯(cuò),只是默默地失敗。如果在嚴(yán)格模式下,則會(huì)報(bào)錯(cuò)。
Object.isFrozen()
Object.isFrozen方法用于檢查一個(gè)對(duì)象是否使用了Object.freeze方法。
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isFrozen(obj) // true
使用Object.freeze方法以后,Object.isSealed將會(huì)返回true,Object.isExtensible返回false。
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isSealed(obj) // true
Object.isExtensible(obj) // false
Object.isFrozen的一個(gè)用途是,確認(rèn)某個(gè)對(duì)象沒(méi)有被凍結(jié)后,再對(duì)它的屬性賦值。
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ò)了。
局限性
上面的三個(gè)方法鎖定對(duì)象的可寫性有一個(gè)漏洞:可以通過(guò)改變?cè)蛯?duì)象,來(lái)為對(duì)象增加屬性。
var obj = new Object(); Object.preventEctensions(obj); var proto = Object.getPrototypeOf(obj); proto.t = 'hello'; obj.t //hello
上面代碼中,對(duì)象obj本身不能新增屬性,但是可以在它的原型對(duì)象上新增屬性,就依然能夠在obj上讀到。
一種解決方案是,把obj的原型也凍結(jié)住。
var obj = new Object(); Object.preventExtensions(obj); var proto = Object.getPrototypeOf(obj); Object.preventExtensions(proto); proto.t = 'hello'; obj.t //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ù)組是可以改變的。
以上就是你必須了解的JavaScript中的屬性描述對(duì)象詳解(下)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript屬性描述對(duì)象的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 獲得Javascript對(duì)象屬性個(gè)數(shù)的示例代碼
- 詳解js對(duì)象中屬性的兩種類型之?dāng)?shù)據(jù)屬性和訪問(wèn)器屬性
- js數(shù)組對(duì)象里面如何獲取某個(gè)屬性值相等的對(duì)象
- JS如何刪除對(duì)象中的某一屬性(delete)
- 你必須了解的JavaScript中的屬性描述對(duì)象詳解(上)
- js刪除對(duì)象屬性的多種方法舉例
- js 如何刪除對(duì)象里的某個(gè)屬性
- Javascript中判斷對(duì)象是否具有屬性的5種方法分享
- JS 實(shí)現(xiàn)獲取對(duì)象屬性個(gè)數(shù)的方法小結(jié)
相關(guān)文章
JavaScript實(shí)現(xiàn)簡(jiǎn)易加法計(jì)算器
這篇文章主要介紹了JavaScript實(shí)現(xiàn)簡(jiǎn)易加法計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
JS實(shí)現(xiàn)按比例縮放圖片的方法(附C#版代碼)
這篇文章主要介紹了JS實(shí)現(xiàn)按比例縮放圖片的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了JavaScript實(shí)現(xiàn)等比例縮放圖片的原理與具體實(shí)現(xiàn)技巧,并附上C#版的圖片縮放代碼做對(duì)比參考,需要的朋友可以參考下2015-12-12
JavaScript實(shí)現(xiàn)隨機(jī)生成驗(yàn)證碼及校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)隨機(jī)生成驗(yàn)證碼及校驗(yàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
javascript實(shí)現(xiàn)無(wú)法關(guān)閉的彈框
本文分享了javascript實(shí)現(xiàn)無(wú)法關(guān)閉的彈框的實(shí)例代碼,感興趣的朋友可以看下2016-11-11
Javascript的構(gòu)造函數(shù)和constructor屬性
我們知道,默認(rèn)情況下,對(duì)一個(gè)函數(shù)前面使用new,可以構(gòu)造出一個(gè)對(duì)象。每一個(gè)對(duì)象都有一個(gè)constructor屬性,這個(gè)constructor屬性指向構(gòu)造出該對(duì)象的函數(shù)。2010-01-01
JS解決?Array.fill()參數(shù)為對(duì)象指向同一個(gè)引用地址的問(wèn)題
這篇文章主要介紹了JS解決?Array.fill()參數(shù)為對(duì)象指向同一個(gè)引用地址問(wèn)題,解決方案使用map返回出不同的引用的地址,fill參數(shù)可隨意填寫(不為空),主要是map函數(shù)中返回的數(shù)據(jù),需要的朋友可以參考下2023-02-02
Javascript節(jié)點(diǎn)關(guān)系實(shí)例分析
這篇文章主要介紹了Javascript節(jié)點(diǎn)關(guān)系,實(shí)例分析了javascript操作父子節(jié)點(diǎn)及兄弟節(jié)點(diǎn)的相關(guān)技巧,需要的朋友可以參考下2015-05-05

