getter 與 setter
說明
Javascript
的物件中,可以透過 getter
與 setter
方法來控制屬性的「訪問」與「修改」。這個方法會讓開發者在「訪問」與「修改」屬性時,執行額外的「程式邏輯」,比如 驗證
或 計算
。
私有屬性
getter
、setter
常用在定義「私有屬性」,可以防止直接訪問到「私有屬性」。定義一個 getter
當屬性被訪問時回傳私有屬性的值,定義一個 setter
當屬性被修改時,修改私有屬性。
例子
在 person
中,定義了 person.firstName
與 person.lastName
來訪問與修改 person
私有屬性 _firstName
、_lastName
。
js
const person = {
_firstName: 'Naiky', // 私有屬性
_lastName: 'Ding', // 私有屬性
// ------ 公有方法 getter setter -----
get fullName() {
return `${this._firstName} ${this._lastName}`
},
// 訪問 firstName 屬性 && 修改屬性
get firstName() {
return this._firstName
},
set firstName(newValue) {
if (!newValue) return false
this._firstName = newValue
return this._firstName
},
// 訪問 lastName 屬性 && 修改屬性
get lastName() {
return this._lastName
},
set lastName(newValue) {
if (!newValue) return false
this._lastName = newValue
return this._lastName
},
}
一般情況
為物件命名一個方法 fullName
可以取得完整的名字,但如果 fullName
被以錯誤的方法賦值了,就無法再執行fullName
這個函式了。
js
const person = {
firstName: 'Naiky',
lastName: 'Ding',
fullName: function () {
return `${this.firstName} ${this.lastName}`
},
}
console.log(person.fullName()) // Naiky Ding
person.fullName = 'NIKI Ding'
console.log(person.fullName()) // Uncaught TypeError: person.fullName is not a function
getter setter 解決什麼事
- 私有化屬性
- 訪問與修改不直接操作私有屬性
getter
在物件中 get
語法以 屬性
作為函式名稱設置。當物件的 屬性
被「訪問」就會執行這個函式。這個方法 class 類 也適用。 (也有點像 vue
的計算屬性 computed
)
語法
get
屬性() { return ... }
get
[動態屬性]() { return ... }
物件設置 getter
js
const person = {
firstName: 'Naiky',
lastName: 'Ding',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
}
console.log(person.fullName) // 'Naiky Ding'
(圖) 可以看到 fullName
的加工。
其它例子
取得資料最後一筆
js
const num = {
array: [1, 2, 3, 4],
get last() {
return this.array[this.array.length - 1]
},
}
console.log(num.last) // 4
刪除 getter
與一般刪除物件屬性方法一樣。
js
delete person.firstName
person.firstName // undefined
為已存在對象定義 getter
可以透過 物件屬性定義 Object.defineProperty 為已經存在的物件,定義 getter
。
語法
Object.defineProperty(物件
, 屬性
, 屬性描述檔
)
js
const obj = { a: 1 }
Object.defineProperty(obj, 'b', {
get() {
return this.a * 5
},
})
console.log(obj.b) // 5
⛔⛔ 注意 ⛔⛔ 屬性無法刪除
若在 物件屬性定義 Object.defineProperty 定義的 getter
會有刪除的可能性,上面的寫法會無法刪除。(重新賦 get 為 undefined)
解決方法
設置時,預先在描述檔設置上 configurable: true
,這樣可以為之後的刪除屬性留後路。 詳細可以看 物件屬性定義 Object.defineProperty
js
const obj = { a: 1 }
Object.defineProperty(obj, 'b', {
configurable: true,
get() {
return this.a * 5
},
})
obj.b // 5
刪除屬性
js
Object.defineProperty(obj, 'b', {
get: undefined,
})
obj.b // undefined
class 設置 getter
js
class Person {
get firstName() {
return 'hello'
}
}
js
const niki = new Person()
console.log(niki.firstName) // hello
刪除 class getter
從 class 刪除原型方法
js
delete Person.prototype.firstName
console.log(niki.firstName) // undefined
class 與 defineProperty 定義 getter 差別
在 class 類 定義的 get
實際上與 Object.defineProperty()
定義的 get
使用上沒有什麼不同?
class
js
class Person {
get hello() {
return `hello`
}
}
const niki = new Person()
niki.hello // 'hello'
const nico = new Person()
nico.hello // 'hello'
// 取得自身屬性
Object.getOwnPropertyDescriptor(niki, 'hello') // undefined
defineProperty
js
const obj = {}
Object.defineProperty(obj, 'hello', {
get() {
return `Hello`
},
})
// 取得自身屬性內容 -> 執行
const helloDec = Object.getOwnPropertyDescriptor(obj, 'hello')
helloDec.get() // Hello
最大差別
- class
get
是定義在 「原型」上的方法,可以被所有「實例」繼承,而不在實例本身。 Object.defineProperty()
是直接定義在指定的物件本身屬性上。
懶加載的 getter
getter
是智能的,有使用到才會執行邏輯;相反的,若都沒有使用到是不會有負擔的開銷。
暫存
可以針對屬性做 lazy
或 delay
以暫存方法儲存供後續訪問,稱為 smart 或暫存作法。 這必須要符合以下條件才適合:
- 訪問數據開銷成本高
- 一次性訪問 or 也可能不會訪問
- 屬性資料訪問過後,不會再修改
原理
初次訪問屬性 -> 執行邏輯 -> 刪除 getter
屬性 -> 重新寫入屬性供訪問暫存數據。
js
get notifier() {
delete this.notifier
this.notifier = document.querySelector('#notifier')
return this.notifier
}
setter
在物件中 set
語法以 屬性作為函式名稱設置。當物件的 屬性被「修改」就會執行這個函式。這個方法 class 類 也適用。常用來修改屬性資料或驗證。
語法
set
屬性() { ... }
set
[動態屬性]() { ... }
物件設置 setter
js
const obj = {
_langs: ['en'],
get langs() {
return this._langs
},
set lang(newLang) {
this._langs.push(newLang)
},
}
obj.langs // ['en']
obj.lang = 'vn'
obj.langs // ['en', 'vn']
驗證例子
js
const person = {
_firstName: 'naiky',
get firstName() {
return this._firstName
},
set firstName(newName) {
if (!newName) {
console.log(`名稱不可為空`)
return false
}
this._firstName = newName
},
}
person.firstName = '' // 名稱不可為空
刪除 setter
js
const langs = {
log: ['en'],
set lang(newLang) {
this.log.push(newLang)
},
}
delete person.lang
注意
setter
與一般屬性同名時,會無法取得。
js
const person = {
firstName: 'naiky',
set firstName(newName) {
if (!newName) {
console.log(`名稱不可為空`)
return false
}
this.firstName = newName
},
}
person.firstName // undefined
為已存在物件新增 setter
使用 Object.defineProperty
可以為已存在的物件定義 setter
。
js
const obj = { a: 0 }
Object.defineProperty(obj, 'plusNum', {
set(newNum) {
this.a += newNum
return this.a
},
})
obj.a // 0
obj.plusNum = 9 // 9
obj.a // 9
刪除 setter
必須在定義 setter
時加入 configurable: true
可方便後續的刪除。 詳細可看 物件屬性定義 Object.defineProperty
js
const obj = { a: 1 }
Object.defineProperty(obj, 'b', {
set(val) {
this.a += val
},
configurable: true,
})
刪除 setter
js
delete obj.b
在 class 設置 setter
js
class Person {
#firstName = 'default'
get firstName() {
return this.#firstName
}
set firstName(newName) {
this.#firstName = newName
return this.#firstName
}
}
const niki = new Person()
niki.firstName // 'default'
niki.firstName = 'NIKI'
niki.firstName // 'NIKI'
刪除 class 中的 setter
js
delete Person.prototype.firstName
class 與 defineProperty setter
差異
- class 定義的
setter
是定全在「原型」之中,所有實例
都會繼承到這個方法。要從 class 原型做刪除。 - defineProperty 是定義在物件本身的屬性上。可參考