由可枚舉屬性引起的
JavaScript屬性分為可枚舉屬性和不可枚舉屬性?什么是“枚舉”,它又有什么用呢?
var apple = {
name : "蘋果",
price :18
}
for(var prop in obj){
console.log(prop)
}
// 輸出:name ,price
// 這里的可枚舉性就是說for的這種寫法可以得到這個對象的屬性名
var stu = {};
Object.defineProperties(stu, {
name: {
value: "張三",
enumerable: false
},
age: {
value: 18,
enumerable: true
}
});
for(var prop in stu){
console.log(prop);
}
// 輸出: age
//name屬性不能便利出來,因為他的 “enumerable” 值為false
//以上代碼請在chrome或者火狐里面運行,IE9以下運行第二段代碼會出錯
我們知道,for...in
語句以任意順序遍歷一個對象的可枚舉屬性。
既 stu.age 可枚舉,stu.name 不可枚舉,而他們是否可枚舉是通過 enumerable
來設置的。
屬性描述符
我們通常會直接創建對象,然后設置對象的屬性,例如上面的 obj 對象,設置了 name ,price 屬性。其實這些屬性也有一定的限定的,這些限定屬性性質的我們稱為“屬性描述符”。
這里拿 price 屬性來說,我們能輸出 fruit.price 等于 18 ,其實是通過 price 的描述符 value
來設置的。例如上面的stu.age 的設置;
price 也有“enumerable”描述符,他之所以能被 for in 到 ,是因為對自身添加屬性的 “enumerable” 默認為 true。
除了上面說到過的 “value” 和 “enumerable” 還有什么描述符呢?
每個屬性都有
Configurable描述符、Enumerable描述符、Writable描述符、Value描述符
或者,每個屬性都有
Configurable描述符、Enumerable描述符、Get描述符、Set描述符
前者稱為 數據描述符,他們的值決定了該js對象的屬性的某些性質及是否可讀寫。
后者稱為 讀取描述符,他們的值決定了該js對象的屬性的某些性質及讀寫的行為。
描述符必須是這兩種形式之一,不能同時是兩者。
那么這些屬性是各代表著什么?
數據描述符具有以下鍵值
鍵 | 值(默認值) | 作用 |
---|---|---|
configurable | false --------------------- | 為 true 時,該屬性描述符才能夠被改變,表示對象的屬性是否可以被刪除,以及除writable特性外的其他特性是否可以被修改。 |
enumerable | false | true時,該屬性在對象中才是可枚舉的 |
value | undefined | 該屬性對應的值。可以是任何有效的 JavaScript 值(數值,對象,函數等)。讀取屬性的時候就是通過這里開始讀 |
writable | false | 表示能否修改屬性的值賦值運算符(assignment operator)基于右值(right operand)的值,給左值(left operand)賦值。")改變 |
讀取描述符具有以下鍵值
鍵 | 值(默認值) | 作用 |
---|---|---|
configurable | false --------------------- | 為 true 時,該屬性描述符才能夠被改變 |
enumerable | false | true時,該屬性在對象中才是可枚舉的 |
get | undefined | 在讀取屬性時調用的函數 |
set | undefined | 在設置屬性時調用的函數 |
知道這些描述符所控制的性質,那又是什么時候去哪里設置的呢?這就關乎到Object.defineProperty() 了,它有三個參數,Object.defineProperty(obj, prop, descriptor),其中descriptor就是設置屬性性質描述符。
defineProperty 和 defineProperties
- 語法
Object.defineProperty(obj, prop, descriptor)
Object.defineProperties(obj, props) - 定義:
Object.defineProperty() 直接在一個對象上定義一個新屬性,或者修改現有屬性,并返回該對象。
Object.definePropertys() 直接在一個對象上定義一個或多個新的屬性,或者修改現有屬性,并返回該對象。 - 參數
obj 要在其上定義屬性的對象。
prop 要定義或修改的屬性的名稱。
descriptor 將被定義或修改的屬性描述符。 - 返回值
返回被操作的對象,即返回obj參數 - 注意點
1)當把configurable值設置為false后,就不能修改任何屬性了,包括自己本身這個屬性
2)想用訪問器屬性模擬默認行為的話,必須得在里面新頂一個屬性,不然的話會造成循環引用
3)可枚舉屬性 對for/in
,Object.keys()
,JSON.stringify()
,Object.assign()
方法才生效(for/in 是對所有可枚舉屬性,而其他三種是對自身可枚舉屬性) - 用途
1)vue通過getter-setter函數來實現雙向綁定
2)俗稱屬性掛載器
3)專門監聽對象數組變化的Object.observe()(es7)也用到了該方法
知道了Object.defineProperty()這個東東是用來生成或修改一個對象屬性,知道對象屬性的性質是靠descriptor這個參數來設置之后,我們來看看他是怎么運用的。
以var person = {}
為例,我們要怎樣去修改默認的屬性值呢?
- 設置該屬性為數據描述符
var person = {}
Object.defineProperty(person,'a',{
configurable:true, //可修改默認屬性
enumerable:true, //可枚舉
writable:true, //可修改這個屬性的值
value:1 //定義一個初始的值為1
})
console.log(person) //{a: 1}
person.a=2
console.log(person) //{a: 2}
for(var k in person){
console.log(k) //a
}
現在我們來修改下enumerable和writable值
Object.defineProperty(person,'a',{
configurable:true,
enumerable:false,
writable:false,
value:1
})
console.log(person) //{a: 1}
person.a=2
console.log(person) //{a: 1} 因為writable值被設置為false了,所以不可以寫,嚴格模式下會報錯
for(var k in person){
console.log(k)// 沒有可枚舉屬性,因為a的enumerable的值被設置為false
}
我們試試吧configurable的值改為false
Object.defineProperty(person,'a',{
configurable:false,//為false的時候不允許修改默認屬性了
})
===============================
# 改為false之后再試試修改其他屬性
Object.defineProperty(person,'a',{
configurable:true,
enumerable:true,
writable:true,
value:1
})
//woa,控制臺直接報錯了!連想把false值改回true都不行!也就是說,這個改動是一次性了!
//也就是說,你可以使用Object.defineProperty()方法無限修改同一個屬性,但是當把configurable改為false之后就什么都不能再修改了
- 設置該屬性為讀取描述符
var person = {
a:1
}
Object.defineProperty(person,'a',{
get(){
return 3 //當訪問這個屬性的時候返回3
},
set(val){
console.log(val)//當設置這個屬性的時候執行,val是設置的值
}
})
person.a// 3,我們明明寫的是a:1,怎么返回的3呢?這就是get()的威力了
person.a = 5// 5,相應的設置的時候執行了set()函數
我們來模擬一個訪問和設置的默認行為
var person = {
a:1
}
// 注:里面的this指向ogj(person)
Object.defineProperty(person,'a',{
get(){
return this.a
},
set(val){
this.a = val
}
})
//我們想當然的這么寫.
person.a//Uncaught RangeError: Maximum call stack size exceeded
什么,溢出了?這是為什么?
哦~原來是這么寫的話會造成循環引用,狂call不止
我們看下流程:
person.a → get.call(person) → this.a → person.a → get.call(person) → this.a......
我們來修改下
var person = {
a:1
}
Object.defineProperty(person,'a',{
get(){
return this._a || 1 //定義一個新的屬性和一個默認值
},
set(val){
this._a = val
}
})
person.a// 1
person.a=2// 2
person.a// 2
這樣就好了
這就是數據描述符和讀取描述符的應用方式。在平時簡單的開發中可能用不上,但是知道了這些之后對一些框架的封裝的理解還是很有幫助的,例如vue數據雙向綁定原理上利用的就是Object.defineProperty方法。
拓展
每個對象都有的一些方法:
Object.getOwnPropertyDescriptors(obj)
定義:獲取一個對象的所有自身屬性(自身屬性)的描述符
使用:Object.getOwnPropertyDescriptors(apple)Object.getOwnPropertyDescriptor(obj, prop)
定義:返回指定對象上一個自有屬性對應的屬性描述符
使用:Object.getOwnPropertyDescriptor(apple, 'price')
Object.getOwnPropertyDescriptor 的應用:
一般直接添加屬性時,屬性描述符默認值都為 true,當用 Object.defineProperty() 方法來添加對象屬性時,此時的屬性描述符默認值為false
以文章開頭的 apple 和 stu 的案例:
// 直接添加屬性
Object.getOwnPropertyDescriptor(apple, 'name')
//name:{value: "蘋果", writable: true, enumerable: true, configurable: true}
// 屬性描述符添加屬性
Object.getOwnPropertyDescriptor(stu, 'name')
//name:{value: "張三", writable: false, enumerable: false, configurable: false}
// 又或者
var stu2 = Object.create({}, { name: { value: '李四' } })
Object.getOwnPropertyDescriptor(stu2, 'name')
//name:{value: "李四", writable: false, enumerable: false, configurable: false}
Object.create()
的第二個參數為 Object.defineProperties
的第二個參數,既設置屬性及屬性描述符, 詳情移步到 Object.create()
參考資料:
1.MDN
3.關于 Object.defineProperty() 小結