在學習原型鏈的同時,自己也有看過關於物件屬性特徵的知識,才知道原來物件屬性裏還有特徵這個東西存在!我們可以定義這些物件的屬性特徵,也可以用一些方法操控或修改它們。
這篇文章會講解:
物件屬性裏有幾個特徵:
我們需要用Object.defineProperty
來定義物件屬性特徵:
Object.defineProperty(物件, 屬性, 設定特徵)
範例如下:
const user = {
a: 10,
b: 20,
c: 30
}
Object.defineProperty(user,'a',{
value: 40,
writable: false,
configurable: true,
enumerable: true
})
//value後面的3個特徵,預設都是true
//因為 writable: false ,所以以下的程式碼是不會有效
user.a = 100;
console.log(user.a) //40
這個例子是修改了屬性a
的值以及writable
特徵,我們重新賦值40,但是因為writable
是false
,所以之後不能再重新賦予新的值。
user.a
會變成一個靜默錯誤,JS執行它的時候並不會報錯。但在嚴謹模式下就會出錯:
(function(){
'use strict';
user.a = 100;
}())
之後我們看看configurable
和enumerable
具體是指什麼。以下例子用defineProperties
同時修改了多個屬性(b和c)的屬性特徵:
Object.defineProperties(user,{
b: {
configurable: false
},
c: {
enumerable: false
}
})
在進行下一步先,可以用Object.getOwnPropertyDescriptor
來看看b和c是不是真的有被改掉特徵:
console.log(Object.getOwnPropertyDescriptor(user,'b'))
console.log(Object.getOwnPropertyDescriptor(user,'c'))
configurable
是指可否被刪除,如果是false
就不能被刪除,所以以下的程式碼是無效:
delete user.b
enumerable
是指可否被例舉,如果是false
就不能被列舉,如果我們用for...in
試試列舉屬性:
for( const key in user ){
console.log(key) //a,b
}
要注意的是,這些屬性的設定只是針對整個物件,所以只是操控了物件裏最外層的屬性,它並不能操控最外層屬性裏的內層屬性,例如以下例子:
Object.defineProperty(user,'outerA',{
value: {},
writable: false,
configurable: false
})
user.outerA = '123'
user.outerA.innerA = 'Hello'
console.log(user)
在以上例子中的outerA
裏新增一個空物件。即使writable
是false
,我們依然可以在那個空物件裏新增屬性innerA
及值Hello
,但就不能重新賦值予outerA
。
以上情況稱為淺層保護,我們不能操控巢狀屬性,只能操控最外層屬性。這裏修改屬性特徵的方式,只是針對物件本身。
除了採用上面提及的方式,逐一修改物件屬性特徵,我們還可以用一些方法,包括preventExtensions
、seal
、freeze
,去設定屬性特徵。
preventExtensions
(禁止擴充):
無法新增屬性,可以重新配置特徵、調整目前屬性值(e.g: 修改屬性、新增巢狀屬性)
seal
(封裝):
無法新增/刪除物件屬性,無法重新配置特徵,但可以調整目前屬性值(e.g: 修改屬性、新增巢狀屬性)
freeze
(凍結):
無法新增/刪除物件屬性,無法重新配置特徵,無法調整目前屬性值(e.g: 修改屬性、新增巢狀屬性)
這裏我們拿其中一個,preventExtensions
做驗證:
例如我們有以下的物件:
const user = {
a: 10,
b: 20,
c: {}
}
preventExtensions的方法:
Object.preventExtensions(user)
//1. 修改屬性 (可以)
user.a = 100
//2. 修改巢狀屬性 (可以)
user.c.inner = 'Hello'
//3. 新增屬性 (不可以)
user.d = 40;
//4. 刪除屬性 (可以)
delete user.a
get
與set
是物件的屬性方法,我們可以先定義一個屬性,並在該屬性裏使用這兩個方法,這些方法的角色有點像function一樣去處理不同的值。
get
:取得特定值的方法set
:存值的方法
先看看set
的用法:
const myScore = {
total: 0,
//把在set運算好的值,送回total
set add(score){
this.total = this.total + score
}
}
myScore.add = 10;
console.log(myScore) //{total: 10}
set
是存值的方法,當執行myScore.add
時,它會把運算好的值,送回total
裏,total
會被重新賦值,所以說set
有存值的功能。
那麼get
呢?看看以下例子:
const myScore = {
total: 0,
//把在set運算好的值,送回total
set add(score){
this.total = this.total + score
},
get add(){
return this.total + 100
}
}
myScore.add = 10;
console.log(myScore.add) //110
console.log(myScore) //{total: 10}
get
是取得特定值的方法,所以執行myScore.add
,會得到經過get
這個特定方法中的add
運算後,得出來的值(110)。可是,當我們直接看myScore
這個物件時,只會顯示total
經過set
方法的後,所更新的值(10),詳細看看console:
我們會見到add
的值以(...)
表示,當我們按下它後,就會顯示110,即是經過get
方法運算出來的值:
如果我們在這裏查詢myScore
和myScore.add
:
const myScore = {
total: 0,
//把在set運算好的值,送回total
set add(score){
this.total = this.total + score
},
get add(){
return this.total + 100
}
}
console.log(myScore.add,myScore) //100, {total: 0} (但(...)裏面是110)
myScore.add = 10;
第一個值會是100,因為目前total
是0,透過add
方法+100,變成100。
第二個值顯示{total: 0} (但(...)裏面是110)
,{total: 0}的原因是total初始值是0。而(...)裏面會是110,因為它把後面的myScore.add = 10
都執行過,total就是 0 + 10 + 100,變成110。
我們可以透過修改物件屬性的特徵,限制別人操控我們定義的屬性。同時,set
和get
的方法也能讓我們像使用function一樣去處理在物件裏,或傳進物件裏的資料。
JS核心篇(六角學院)
JAVASCRIPT.INFO
重新認識 JavaScript: Day 22 深入理解 JavaScript 物件屬性