做者:Khalil Stemmler翻譯:瘋狂的技術宅javascript
原文:https://www.freecodecamp.org/...html
未經容許嚴禁轉載前端
在本文中,咱們討論了getter 和 setter 在現代 Web 開發中的實用性。它們有用嗎?何時使用它們是有意義的?
當 ECMAScript 5(2009)發佈時,getters 和 setter(也稱爲訪問器)被引入 JavaScript。vue
問題是,對於引入它們的緣由及實用性存在不少困惑。java
我在 reddit 看到了一個帖子,討論的內容是它們是不是反模式。react
不幸的是,該主題的廣泛共識是 「yes」。我認爲這是由於大多數狀況下,你所作的前端編程都不會要求提供 getter 和 setter 這樣的操做。git
儘管我不一樣意 getter 和 setter 徹底是一個反模式。但它們在幾種狀況下能帶來更多的實用性。程序員
getter 和 setter 是另外一種提供對象屬性訪問的方法。github
通常的用法以下所示:面試
interface ITrackProps { name: string; artist: string; } class Track { private props: ITrackProps; get name (): string { return this.props.name; } set name (name: string) { this.props.name = name; } get artist (): string { return this.props.artist; } set artist (artist: string) { this.props.artist = artist; } constructor (props: ITrackProps) { this.props = props; } public play (): void { console.log(`Playing ${this.name} by ${this.artist}`); } }
如今問題變成了:「爲何不僅使用常規類屬性?」
那麼,在這種狀況下,是能夠的。
interface ITrackProps { name: string; artist: string; } class Track { public name: string; public artist: string; constructor (name: string, artist: string;) { this.name = name; this.artist = artist; } public play (): void { console.log(`Playing ${this.name} by ${this.artist}`); } }
這是一個很是簡單的例子,讓咱們來看一個更好地描述,爲何咱們應該關心使用 getter 和 settter 與常規類屬性的場景。
你還記得貧血模式(譯者注:一種反模式)是什麼嗎?儘早發現貧血模式的方法之一是,假如你的域實體的每一個屬性都有getter和setter(即:set 對域特定語言沒有意義的操做)暴露的話。
若是你沒有明確地使用 get
或 set
關鍵字,那麼會使全部 public
也有相同的負面影響。
思考這個例子:
class User { // 很差。你如今能夠`set` 用戶ID。 // 是否須要將用戶的 id 變動爲其餘標識符? // 這樣安全嗎? 你應該這樣作嗎? public id: UserId; constuctor (id: UserId) { this.id = id; } }
在領域驅動設計中,爲了防止出現貧血模式,並推動特定於領域的語言的建立,對於咱們僅公開對領域有效的操做很是重要。
這意味着你須要瞭解本身正在工做的領域。
我會讓本身接受審查。讓咱們來看看 White Label 中的 Vinyl
類,這是一個開源的乙烯基交易程序,使用領域驅動進行設計並基於 TypeScript 構建。
import { AggregateRoot } from "../../core/domain/AggregateRoot"; import { UniqueEntityID } from "../../core/domain/UniqueEntityID"; import { Result } from "../../core/Result"; import { Artist } from "./artist"; import { Genre } from "./genre"; import { TraderId } from "../../trading/domain/traderId"; import { Guard } from "../../core/Guard"; import { VinylCreatedEvent } from "./events/vinylCreatedEvent"; import { VinylId } from "./vinylId"; interface VinylProps { traderId: TraderId; title: string; artist: Artist; genres: Genre[]; dateAdded?: Date; } export type VinylCollection = Vinyl[]; export class Vinyl extends AggregateRoot<VinylProps> { public static MAX_NUMBER_GENRES_PER_VINYL = 3; //🔥1. 外觀。 VinylId 鍵實際上並不存在 //做爲屬性的 VinylProps,但咱們仍然須要 //提供對它的訪問。 get vinylId(): VinylId { return VinylId.create(this.id) } get title (): string { return this.props.title; } // 🔥2. 全部這些屬性都做爲 props 嵌套 // 在一層,這樣咱們就能夠控制對 ACTUAL 值 // 的訪問和變化。 get artist (): Artist { return this.props.artist } get genres (): Genre[] { return this.props.genres; } get dateAdded (): Date { return this.props.dateAdded; } // 🔥3. 你會發現到目前爲止尚未 setter, // 由於在建立以後去改變這些東西是沒有意義的 get traderId (): TraderId { return this.props.traderId; } // 🔥4. 這種方法稱爲「封裝集合」。 // 是的,咱們須要添加類型。 但咱們仍 // 然沒有公開 setter,由於這裏有一 // 些咱們想要確保強制執行的不變邏輯。 public addGenre (genre: Genre): void { const maxLengthExceeded = this.props.genres .length >= Vinyl.MAX_NUMBER_GENRES_PER_VINYL; const alreadyAdded = this.props.genres .find((g) => g.id.equals(genre.id)); if (!alreadyAdded && !maxLengthExceeded) { this.props.genres.push(genre); } } // 🔥 5. 提供一種刪除方式。 public removeGenre (genre: Genre): void { this.props.genres = this.props.genres .filter((g) => !g.id.equals(genre.id)); } private constructor (props: VinylProps, id?: UniqueEntityID) { super(props, id); } // 🔥 6. 這就是咱們建立 Vinyl 的方法。 // 建立以後,除了 Genre 以外,全部屬性 // 都會變爲「只讀」,由於啓用修改是有意義的。 public static create (props: VinylProps, id?: UniqueEntityID): Result<Vinyl> { const propsResult = Guard.againstNullOrUndefinedBulk([ { argument: props.title, argumentName: 'title' }, { argument: props.artist, argumentName: 'artist' }, { argument: props.genres, argumentName: 'genres' }, { argument: props.traderId, argumentName: 'traderId' } ]); if (!propsResult.succeeded) { return Result.fail<Vinyl>(propsResult.message) } const vinyl = new Vinyl({ ...props, dateAdded: props.dateAdded ? props.dateAdded : new Date(), genres: Array.isArray(props.genres) ? props.genres : [], }, id); const isNewlyCreated = !!id === false; if (isNewlyCreated) { // 🔥 7. 這就是咱們須要 VinylId 的緣由: // 爲這個域事件的全部訂閱者提供標識符。 vinyl.addDomainEvent(new VinylCreatedEvent(vinyl.vinylId)) } return Result.ok<Vinyl>(vinyl); } }
充當外觀、維護只讀值、強制執行模型表達、封裝集合以及建立域事件是領域驅動設計中 getter 和 setter 的一些很是可靠的用例。
Vue.js 是一個較新的前端框架,以其快速和響應式而聞名。
Vue.js 可以如此有效地檢測改變的緣由是它們用 Object.defineProperty()
API 去監視對 View Models 的更改!
來自 Vue.js 關於響應式的文檔:
當你將純 JavaScript 對象做爲其數據選項傳遞給 Vue 實例時,Vue 將遍歷其全部屬性並用 Object.defineProperty 將它們轉換爲 getter/setter。 getter/setter 對用戶是不可見的,可是在幕後,它們使 Vue 可以在訪問或修改屬性時執行依賴關係跟蹤和更改通知。 —— Vue.js 文檔:響應式
總之,getter 和 setter 針對不少問題有很大的實用性。不過在現代前端 Web 開發中,這些問題並無太多出現。