做者:Khalil Stemmlerjavascript
翻譯:瘋狂的技術宅html
原文:www.freecodecamp.org/news/typesc…前端
未經容許嚴禁轉載vue
在本文中,咱們討論了getter 和 setter 在現代 Web 開發中的實用性。它們有用嗎?何時使用它們是有意義的?java
當 ECMAScript 5(2009)發佈時,getters 和 setter(也稱爲訪問器)被引入 JavaScript。react
問題是,對於引入它們的緣由及實用性存在不少困惑。git
我在 reddit 看到了一個帖子,討論的內容是它們是不是反模式。github
不幸的是,該主題的廣泛共識是 「yes」。我認爲這是由於大多數狀況下,你所作的前端編程都不會要求提供 getter 和 setter 這樣的操做。typescript
儘管我不一樣意 getter 和 setter 徹底是一個反模式。但它們在幾種狀況下能帶來更多的實用性。編程
getter 和 setter 是另外一種提供對象屬性訪問的方法。
通常的用法以下所示:
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 開發中,這些問題並無太多出現。