這是一個簡單的 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/