iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Modern Web

舌尖上的JS系列 第 19

D19 - 今晚我想來點 唯獨派 getter 唯寫派 setter

前言

JavaScript 內的物件都有內建的兩個屬性,可以實現對物件的存取,稱為:

  • getter 取值器
  • setter 設值器

他們是什麼

不同於一般物件內的屬性,由 getter 和 setter 定義的屬性有個專有名詞稱為 存取器屬性 accessor property

寫在 getter 或 setter 的屬性皆為方法,但在查找時,不需加上() 呼叫,背後會自動進行函式呼叫。

兩者的差異?

getter 取值器,主要目的是讀取值,不能進行賦值修改
setter 設值器,可以設定屬性的值,透過賦值的方式傳入參數

屬性依照是否設置 setter 與 getter 分為三種特性:

  • 可讀寫特性:同時設置 getter、setter
  • 唯獨特性 read-only:只設置 getter
  • 唯寫特性 write-only:只設置 setter

如何設定取值器和設值器

在 JS 中有三種方法:

  1. 物件實值 Object literal
  2. class 類別定義
  3. Object.defineProperty

1. 物件實質 Object literal 設定

//物件實質中的寫法
let obj = {
        get name(){
    ....
    },
        set name(value){
        ...
    }
}

setter、getter 可用來定義可計算屬性 computed property,其值是每次存取時計算出來的,像是物件中若有屬性 firstName 與 lastName,可以設置一個存取器屬性 fullName ,透過 setter 設定將 firstName 與 lastName 屬性相加印出。

❓ 我有疑問
其實可以在物件內建立一個方法就可以,為什麼要設置為 setter 呢?
根據忍者 2 的講解,若某個值只依賴自物件的內部狀態(如:firstName、lastName),比起以方法形式呈現,以屬性的方式做讀取其實更合理!

實際操作如下:

let aurther = {
    firstName : 'Sherry',
    lastName : 'Ho',
    get fullName(){
    consle.log(this.firstName + this.lastName)
    },
    set fullName(name){
        let _name_ = name.split(' ')
        this.firstName =  _name_[0]
        this.lastName = _name_[1]
    }
}

aurther.fullName   // SherryHo,呼叫 getter 取值
aurther.fullName = 'Sam Smith' // 重新指派值會呼叫 setter 將新值當參數傳入
aurther.firstName  // Sam,變為 setter 新賦的值
aurther.fullName  // Sam Smith

在 DevTools 印出可以看到屬於存取器屬性前會加上 setter 與 getter 字樣。

2. Class 中設定 setter、getter

class 內加上 setget 關鍵字,就可以定義存取器屬性。

// class 內寫法
class Name{
    construcor(){}
    set name(){}
    get name(value){}
}

舉例:
定義一個 class 名稱為 Cake,設置一個可讀寫的屬性 producer,這個存取器屬性會存入 prototype 物件內,以 Cake 創建的實例可以透過原型鏈取得。

class Cake {
    constructor(flavor){
        this.flavor = flavor;
        this._producer = 'Hoo'
    }
    set producer(name){
        this._producer = name;
    }
    get producer(){
        return this._producer;
    }
}

let cake1 = new Cake('Tiramisu')
cake1.producer  // Hoo,實例可透過原型鏈取得 producer 屬性

在 DevTools 中印出可以看到,存取器屬性 producer 存在於 prototype 內

3. Object.defineProperty

透過 Object.defineProperty 可以定義或修改物件中的屬性。

Object.defineProperty(目標物件,'特性名稱', {setter, getter 描述})
let cake = {};
Object.defineProperty(cake, 'producer', {
    get: function (){
        return this.producer
    },
    set: function (name){
        this.producer = name;
    }
})

可以透過 Object.defineProperty 來控制存取私有變數,與物件實質和 class 不同的是,透過 Object.defineProperty 會和私有變數建立在相同的範圍中!

function Cake() {
    let _cakeRating = 0;
Object.defineProperty(this, 'cakeRating', {
    get: ()=>{
        return _cakeRating;
    },
    set: (value)=>{
        _cakeRating = value;
    }
})
}

let cake1 = new Cake();
cake1.cakeRating = 10; // 透過 getter 指派值給 _cakeRating 變數
console.log( cake1.cakeRating ) // 10,只能透過 getter 取到 _cakeRating 這個私有變數

嚴格模式禁止修改 getter

若對 getter 屬性重新賦值,在一般模式下不會產生效果,引擎會自動忽略這項動作,但在嚴格模式下禁止這項動作,會拋出錯誤提醒不能對唯讀屬性修改。

'use strict'

class Cake {
    constructor(flavor){
        this.flavor = flavor;
        this._productionDate = 'Oct.3, 2021'
    }
    set productionDate(date){
        this._productionDate = date;
    }
    get producer(){
        return 'this.Hoo';
    
    }}

let cake1 = new Cake('cream')
a.producer = 'sherry' // error! Cannot set property producer of #<Cake> which has only a getter

Reference

008重新認識 JavaScipt
忍者 JavaScript 開發技巧探秘第二版 by John Resig, Bear Bibeault, Josip Maras
MDN


上一篇
D18 - 吃一顆 Class 語法糖 (下)比較 Constructor 與 Class
下一篇
D20 - 濃濃咖啡香的深拷貝、淺拷貝
系列文
舌尖上的JS30

尚未有邦友留言

立即登入留言