Semantic
class MyComponent extends BaseComponent {
constructor() {
this.state = {foo: 42}
}
wrongUsage() {
this.state.foo = ...
this.state = ...
}
}
class BaseComponent {
get state() { return this._immutableState }
set state(initState) {
if (this._state != null) throw new TypeError('please use setState()')
if (initState == null) throw new TypeError('state should be initialized to a non-null value')
this._state = initState
this._immutableState = new Proxy(this._state, { set() { throw new TypeError() } })
}
}
class MyComponent extends BaseComponent {
state = {foo: 42}
wrongUsage() {
this.state.foo = ...
this.state = ...
}
}
class User {
name
password
...
}
class SafeUser extends User {
password = generateStrongPassword()
}
class User {
get password() { return this._password }
set password(v) {
this._password = v
SecurityAuditor.log('user change password', this.name, v)
}
}
class User {
constructor() {
this.password = generateStrongPassword()
...
}
}
class SafeUser extends User {
get password() { return this._password }
set password(v) {
this._password = v
SecurityAuditor.log('user change password', this.name, v)
}
}
class User {
constructor() {
this.password = generateStrongPassword()
...
}
}
class User {
password = generateStrongPassword()
...
}
-
Subclass field will never call superclass accessor
-
Subclass accessor can not override superclass field
-
Ok: Babel 6, TypeScript
-
Fail: Babel 7, Chrome
[[Define]]
VS [[Set]]
[[Set]]
Babel 6, TypeScript[[Define]]
Babel 7, Chrome
Why [[Set]]
- No footgun
foo = bar
just implies[[Set]]
Why [[Define]]
-
Definition!
-
Decorator!
-
[[Define]]
Vue, React, ... -
[[Set]]
MobX, Polymer, ...
No real consensus
Possible Solution
New ESLint rule prefer-decorator-for-field
class {
@set foo = 42
@define bar = 1337
}
- Burden for edge cases
- @define still broken — subclass
accessor can't override superclass
No public field!
Classes 1.1 proposal by Kevin Smith, Allen Wirfs-Brock and Brendan Eich
class Counter {
var value = 42
inc() {
++value
}
get value() { return value }
equal(that) {
return value === that::value
// this::value === that::value
}
}
No public field!
Own property Definition
Prototype-based Inheritence
Refused because of no public field...
Revisit the issues
-
foo = bar
implies[[Set]]
-
As
[[Set]]
, programmers expect
(subclass) "field" call (superclass) accessor -
As
[[Define]]
, programmers expect
subclass (accessor) override superclass (field) -
[[Define]]
-
own property
Solutions
-
Drop
[[Define]]
(use[[Set]]
) -
Drop own property (use
[[Define]]
on prototype) -
Drop the combination of
[[Define]]
and own property -
use [[Set]] -
[[Define]]
accessors on prototype -
[[Define]]
data property on prototype, then[[Set]]
for initializer
class Foo {
<keyword> foo = 42
}
class Bar extends Foo {
get foo() { ... }
set foo(v) { ... }
}
class Foo {
// Object.defineProperty(Foo.prototype,
// 'foo', { value: undefined, ... })
foo
constructor() {
this.foo = 42
}
}
class Bar extends Foo {
get foo() { ... }
set foo(v) { ... }
}
Syntax sugar of getter/setter wrapper for private
class Foo {
<keyword> foo = 42
}
class Foo {
#foo = 42
get foo() { return this.#foo }
set foo(v) { this.#foo = v }
}
class Foo {
<private> foo = 42
get foo() { return foo }
set foo(v) { foo = v }
<private> method() {
foo // diff from this.foo
}
}
Prototype-based Follow ES6 classes
Keyword-based Easy to follow OO best practice
<keyword> foo = 'foo'
<keyword> writable bar = 'bar'
#foo = 'foo'
get foo() { return this.#foo }
#bar = 'bar'
get bar() { return this.#bar }
set bar(v) { this.#bar = v }
getter/setter is bad?
No!
(Time limit, no details)
Most programming languages are using syntax sugar of getter/setter pattern!
- C#
- Ruby
- Scala
- Groovy
- Kotlin
- Dart
- Swift
- ...
all use this pattern
In last 20 years, main stream OO programming languages all adopt the design of using getter/setters wrap private states — which is proved as OO best practice.
And most programming languages designers agree it's good and deserve dedicate syntax
JavaScript programmers don't use getter/setters much, not because we dislike getter/setters, just because we never have private mechanism (there are some workarounds, but lack of ergonomics syntax), which means we eventually have to store states to the own properties in most cases.
So we use own properties to store states of instances just because we do not have other good choices
class Counter {
count = 0
inc() {
++this.count
}
}
class CounterStartFrom100 extends Counter {
count = 100
}
class Counter {
#count = 0
get count() { return this.#count }
inc() {
++this.#count
}
}
const c = new CounterStartFrom100()
c.inc()
c.count // expect 101, actual 100
class BetterCounter extends Counter {
#count = 0
get count() { return this.#count }
inc() {
++this.#count
}
}
const c = new BetterCounter()
c.inc()
return c.count === 0 // expect 1, actual 0
- 子类会破坏父类的约束
- 父类也会破坏子类的约束
无意 vs 有意
code review 负担
[[Define]]
[[Set]]
[[Set]]
Babel 6, TypeScript[[Define]]
Babel 7, Chrome
Why [[Define]]
- Definition!
- Syntax cost!
- Decorator!
Poll?
-
[[Define]]
-
[[Set]]
-
[[Define]]
Vue, React? -
[[Set]]
MobX, Polymer
[[Define]]
But change syntax?
foo := 1
foo
foo: type
foo: type := bar
declare foo
-
declare #foo
-
declare static foo
-
declare static #foo
-
declare method()
-
Syntax cost
-
Consistency
Poll?
[[Define]]
[[Define]]
change syntax[[Set]]
No public field!
Classes 1.1
Own property Definition
Prototype-based Inheritence
Possible Solution
Syntax sugar of getter/setter wrapper for private
expose #foo
#foo
get foo() { return this.#foo }
set foo(v) { this.#foo = v }
原型
OO 最佳实践
expose readonly #foo
expose writable #foo
readonly foo = 'foo'
writable bar = 'bar'
#foo = 'foo'
get foo() { return this.#foo }
#bar = 'bar'
get bar() { return this.#bar }
set bar(v) { this.#bar = v }
getter/setter is bad?
-
广泛接受?
-
性能差?
-
语义假定?
-
暴露 mutable?
-
own prop可反射!
-
Destructuring
-
Object.assign
-
Object.keys/values/entries
接口实现 对象工厂
封装 对象状态 暴露 对象状态
接口实现 class 对象工厂 factory
React
class Counter extends React.Component {
constructor {
super()
this.state = {count: 0}
this.inc = this.inc.bind(this)
}
inc() {
this.setState({count: this.state.count + 1})
}
render() {
return (
<div onClick={this.inc}>{this.state.count}</div>
)
}
}
class Counter extends React.Component {
state = {count: 0}
inc = () => {
this.setState({count: this.state.count + 1})
}
render() {
return (
<div onClick={this.inc}>{this.state.count}</div>
)
}
}
React.defineComponent(({state}) => {
state.init({count: 0})
\
function count() {
return state.current().count
}
function inc() {
state.next({count: count() + 1})
}
\
return props => (
<div onClick={inc}>{count()}</div>
)
})
React.defineComponent(({state}) => {
const [count, nextCount] = state.create(0)
\
function inc() {
nextCount(count() + 1)
}
\
return props => (
<div onClick={inc}>{count()}</div>
)
})
class Counter extends React.Component {
constructor {
super()
this.state = {count: 0}
this.inc = this.inc.bind(this)
}
inc() {
this.setState({count: this.state.count + 1})
}
render() {
return (
<div onClick={this.inc}>{this.state.count}</div>
)
}
}
其他采用getter/setter 包装语法糖方案的语言
C# Ruby Scala Kotlin Groovy Swift
Все счастливые семьи похожи друг на друга каждая несчастливая семья несчастлива по-своему
Happy families are all alike; every unhappy family is unhappy in its own way.
Happy classes are all alike; every unhappy class is un- happy in its own property.
基于 Getter/Setter 封装 private 状态是过去20年各主流语言的 class 中广泛采用并已经证明的最佳实践!
JavaScript程序员少用 getter/setter 绝不是因为我们不喜欢 getter/setter 更不是因为 getter/setter is bad 只是因为我们没有 private,状态 最后还是要存到 own properties 上
JavaScript 用 own properties 保存实例的数据状态只是因为我们 别无选择
Most programming languages designers agree that, Getter/Setter is GOOD.
Poll
[[Define]]
own property[[Define]]
own property, change syntax[[Set]]
own property- getter/setter