ES6的Public屬性和Private屬性

這是一個簡單的 Player 類,它包含了咱們討論的有關 ES6 類的全部內容。javascript

JavaScript 代碼:java

class Player {
  constructor() {
    this.points = 0
    this.assists = 0
    this.rebounds = 0
    this.steals = 0
  }
  addPoints(amount) {
    this.points += amount
  }
  addAssist() {
    this.assists++
  }
  addRebound() {
    this.rebounds++
  }
  addSteal() {
    this.steals++
  }
}

咱們看看這段代碼,咱們能不能讓它更直觀一點呢?方法很好理解,都很天然。那麼構造函數呢?什麼是 constructor ?爲何咱們必須在這裏定義實例值?如今,這些問題已經有了答案,可是爲何咱們不能向實例中添加 state(狀態) ,就像方法那樣?好比:babel

JavaScript 代碼:函數

class Player {
  points = 0
  assists = 0
  rebounds = 0
  steals = 0
  addPoints(amount) {
    this.points += amount
  }
  addAssist() {
    this.assists++
  }
  addRebound() {
    this.rebounds++
  }
  addSteal() {
    this.steals++
  }
}

事實上,這是 Class Fields Declaration 提案的基礎,該提案目前處於 TC-39 流程的 第3階段 。 此提議容許您直接將實例屬性添加爲類的屬性,而無需使用構造方法。 很是漂亮,可是若是咱們看一些 React 代碼,這個提案真的很棒。 這是一個典型的 React 組件。 它具備本地 state(狀態) ,一些方法以及一些靜態屬性被添加到類中。性能

JavaScript 代碼:ui

class PlayerInput extends Component {
  constructor(props) {
    super(props)
    this.state = {
      username: ''
    }
 
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}
 
PlayerInput.propTypes = {
  id: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  onSubmit: PropTypes.func.isRequired,
}
 
PlayerInput.defaultProps = {
  label: 'Username',
}

讓咱們看看新的 Class Fields 提議如何改進上面的代碼首先,咱們能夠將 state(狀態) 變量從構造函數中取出,並將其直接定義爲類的屬性(或「字段」)。this

JavaScript 代碼:prototype

class PlayerInput extends Component {
  state = {
    username: ''
  }
  constructor(props) {
    super(props)
 
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}
 
PlayerInput.propTypes = {
  id: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  onSubmit: PropTypes.func.isRequired,
}
 
PlayerInput.defaultProps = {
  label: 'Username',
}

很酷,但沒什麼好興奮的。 咱們繼續吧。 在上一篇文章中,咱們討論瞭如何使用 static 關鍵字向類自己添加靜態方法。 可是,根據 ES6 類規範,這隻對方法有效,對於值則無效。 這就是爲何在上面的代碼中,咱們必須在咱們定義完 PlayerInput 以後,再在 class 外面將 propTypes 和 defaultProps 添加到 PlayerInput ,而不是在 class 體內定義他們的緣由。 再說一遍,它們不能像靜態方法那樣直接放入 class 體內呢? 好消息是,這也包含在 Class Fields 提案中。 因此如今不只能夠在類體中定義靜態方法,還能夠定義靜態值。 這對咱們的代碼意味着咱們能夠將 propTypes 和 defaultProps 移動到 class 體內定義。插件

JavaScript 代碼:code

class PlayerInput extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    onSubmit: PropTypes.func.isRequired,
  }
  static defaultProps = {
    label: 'Username'
  }
  state = {
    username: ''
  }
  constructor(props) {
    super(props)
 
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}

這樣代碼看上去好多了,但咱們仍然有醜陋的 constructor 方法和 super 調用。 一樣,咱們如今須要構造函數的緣由是爲了將 handleChange 方法綁定到恰當的上下文中。 若是咱們能找到另外一種方法來確保始終在恰當的上下文中調用 handleChange ,那麼咱們能夠擺脫掉 constructor 。

若是您之前使用過箭頭函數,就會知道它們沒有本身的 this 關鍵字。相反,this 關鍵字是按 lexically(詞法) 綁定的。這是一種奇特的說法,當你在箭頭函數中使用 this 關鍵字時,事情會按照你所指望的方式運行。利用這些知識並將其與 「Class Fields」 提案相結合起來,若是咱們將 handleChange 方法替換爲箭頭函數呢?這看起來有點奇怪,可是經過這樣作,咱們能夠解決綁定問題,由於,箭頭函數是經過 lexically(詞法) 綁定 this 的。

JavaScript 代碼:

class PlayerInput extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    onSubmit: PropTypes.func.isRequired,
  }
  static defaultProps = {
    label: 'Username'
  }
  state = {
    username: ''
  }
  handleChange = (event) => {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}

你看上面的代碼,這比咱們開始的原始類要好得多,這都要感謝 「Class Fields」 提案,它將很快成爲 EcmaScript 規範的一部分。

從開發者體驗的角度來看,Class Fields 提案優點很明顯。 然而,他們有一些缺點,不多被談論。 在上一篇文章中,咱們討論了 ES6 類實際上只是 Pseudoclassical Instantiation(僞類實例化) 模式的語法糖。也就是說,當你向類添加方法時,這就像在函數原型中添加方法同樣。

JavaScript 代碼:

class Animal {
  eat() {}
}
 
// 等價於
 
function Animal () {}
Animal.prototype.eat = function () {}

這是高效的,由於 eat 定義一次並在類的全部實例之間共享。 這與 Class Fields 有什麼關係? 好吧,正如咱們上面所看到的, Class Fields 被添加到實例中。 這意味着對於咱們建立的每一個實例,咱們將建立一個新的 eat 方法。

JavaScript 代碼:

class Animal {
  eat() {}
  sleep = () => {}
}
 
// 等價於
 
function Animal () {
  this.sleep = function () {}
}
 
Animal.prototype.eat = function () {}

請注意 sleep 如何放在實例上,而不是放在 Animal.prototype 上。這是件壞事嗎?嗯,有可能。在不進行度量的狀況下對性能進行寬泛的描述一般不是一個好主意。您須要在應用程序中回答的問題是,您從 Class Fields 中得到的開發人員體驗是否超過了潛在的性能損失。

若是你想在你的應用程序中使用咱們以前談到的任何內容,你須要使用 babel-plugin-transform-class-properties 插件。

Private(私有) 屬性 Class Fields 提案的另外一個內容時是 「private fields (私有屬性)」 。 有時,當您構建一個類時,您但願擁有不暴露給外界的私有值。 從歷史上看, JavaScript 缺少真正私有值 的能力,因此咱們經過約定,用下劃線標記它們。

JavaScript 代碼:

class Car {
  _milesDriven = 0
  drive(distance) {
    this._milesDriven += distance
  }
  getMilesDriven() {
    return this._milesDriven
  }
}

在上面的示例中,咱們依靠 Car class(類)的實例經過調用 getMilesDriven 方法來獲取汽車的里程數。可是,由於沒有什麼能使 _milesDriven成爲私有的,因此任何實例均可以訪問它。

JavaScript 代碼:

const tesla = new Car()
tesla.drive(10)
console.log(tesla._milesDriven)

有個奇特的(hacky)方法,就是使用 WeakMaps 能夠解決這個問題,但若是存在更簡單的解決方案,那將會很好。 一樣,Class Fields 提案正在拯救咱們。 根據提議,您可使用 # 建立私有字段。 是的,你沒有看錯, # 。 咱們來看看它對咱們的代碼有什麼影響,

JavaScript 代碼:

class Car {
  #milesDriven = 0
  drive(distance) {
    this.#milesDriven += distance
  }
  getMilesDriven() {
    return this.#milesDriven
  }
}

咱們能夠用速記語法更進一步簡化

JavaScript 代碼:

class Car {
  #milesDriven = 0
  drive(distance) {
    #milesDriven += distance
  }
  getMilesDriven() {
    return #milesDriven
  }
}
 
const tesla = new Car()
tesla.drive(10)
tesla.getMilesDriven() // 10
tesla.#milesDriven // Invalid

目前 有一個 PR 將私有屬性添加到 Babel ,以便您能夠在應用中使用它們。

英文原文:https://tylermcginnis.com/javascript-private-and-public-class-fields/

相關文章
相關標籤/搜索