TS與JS中的Getters和Setter究竟有什麼用

做者:Khalil Stemmlerjavascript

翻譯:瘋狂的技術宅html

原文:www.freecodecamp.org/news/typesc…前端

未經容許嚴禁轉載vue

No, Getters and Setters in TypeScript & JavaScript aren't useless

在本文中,咱們討論了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 對域特定語言沒有意義的操做)暴露的話。

若是你沒有明確地使用 getset 關鍵字,那麼會使全部 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 是一個較新的前端框架,以其快速和響應式而聞名。

Vue.js 可以如此有效地檢測改變的緣由是它們用 Object.defineProperty() API監視對 View Models 的更改!

來自 Vue.js 關於響應式的文檔:

當你將純 JavaScript 對象做爲其數據選項傳遞給 Vue 實例時,Vue 將遍歷其全部屬性並用 Object.defineProperty 將它們轉換爲 getter/setter。 getter/setter 對用戶是不可見的,可是在幕後,它們使 Vue 可以在訪問或修改屬性時執行依賴關係跟蹤和更改通知。 —— Vue.js 文檔:響應式


總之,getter 和 setter 針對不少問題有很大的實用性。不過在現代前端 Web 開發中,這些問題並無太多出現。

歡迎關注前端公衆號:前端先鋒,領取前端工程化實用工具包。

相關文章
相關標籤/搜索