Why
I feel like they'd be more useful than private fields for scenarios outside of classes.
Also if Symbol.private()
ever came out, who would actually keep using Symbol()
? I feel like most people would want to migrate to Symbol.private()
.
I feel that Symbols read nicer when associating state with objects:
const someValue = new WeakMap()
someValue.set(someObject, 123)
log( someValue.get(someObject) )
vs
const someValue = Symbol.private()
someObject[someValue] = 123
log( someObject[someValue] )
Besides the index access syntax is more useful:
someObject[someValue]++
someObject[someValue] += 123
// --
// *=
// etc
Symbols are also a smidge faster than WeakMap in V8 as of writing this:
Syntax sugar?
What would syntax sugar look like?
const someValue = Symbol.private()
someObject.!someValue = 123
log( someObject.!someValue )
For classes, could the sugar create the binding outside of the class automatically? F.e.
class SomeClass {
!someValue = 123
}
const someValue = 123 // SyntaxError, someValue has already been declared.
or
const someValue = 123
class SomeClass {
!someValue = 123 // SyntaxError, someValue has already been declared.
}
Basically this,
class SomeClass {
!someValue = 123
}
would be effectively the same as
const someValue = Symbol.private('someValue') // name determined syntactically
class SomeClass {
[someValue] = 123
}
And then, because the declared variable is in scope, it is usable anywhere in the class, as well as anywhere in the scope containing the class, matching the same semantics as without syntax sugar:
import {someFunction} from 'somewhere'
export class SomeClass {
!someValue = 123
someMethod() {
console.log(this.!someValue)
someFunction(this, someValue) // pass the private key to allow private access to certain API, etc
anotherFunction(this)
}
}
function anotherFunction(instance) {
// This works! No limitation on syntactic location like #private fields!
console.log(instance.!someValue)
}
Just as with any other undefined variable, a typo would result in a ReferenceError
:
class SomeClass {
!someValue = 123
}
const o = new SomeClass()
o.!someVale // ReferenceError: someVale is not defined
If we had this syntax, then maybe .!foo
would just be short for [foo]
, and would work for any variable?
F.e.:
const prop = 'foo' // string property
o.!prop // it works
The class syntax !foo = 123
would be unique to classes for the field declarations, without the period.
For objects too?
const o = {
// declares the variable in the scope that `o` is defined in.
!someValue: 123
}
console.log( o.!someValue ) // 123
Encapsulate it, for example:
function foo() {
return class {
!someValue = 123
method() {
console.log( this.!someValue ) // 123
}
}
}
const someValue = 123 // ok, not declared in outer scope
const Class = foo()
const o = new Class()
console.log( o.!someValue ) // undefined because property "123" does not exist
Another option could be to make it module scoped:
function foo() {
return class {
!someValue = 123
method() {
console.log( this.!someValue ) // 123
}
}
}
const someValue = 123 // Error: someValue has already been declared
Perhaps in the immediate scope surrounding the class or object is better, more flexible, more options on how to organize? For example,
const someValue = Symbol.private()
function foo() {
class Foo {
[someValue] = 123
!otherValue = 456
method() {
console.log( this.!someValue ) // 123
console.log( this.!otherValue ) // 456
}
}
const o = { !otherValue: 'blah' }
console.log( o.!otherValue ) // "blah"
return Foo
}
// here only `someValue` is visible outside the function
const o = new (foo())()
o.!someValue // 123
o.!otherValue // ReferenceError: otherValue is not defined
Also,
class Foo {
static !someValue = 123
get !foo() {...}
set !foo() {...}
!method() {...}
accessor !bar = 456
}
console.log(someValue, foo, method, bar) // PrivateSymbol(someValue), PrivateSymbol(foo), PrivateSymbol(method), PrivateSymbol(bar)
Reasoning for the Sugar
The reasoning behind this syntax sugar idea, namely with class and object definitions, is to match a very similar semantic as we would have if we were using symbols without syntax sugar (with one difference: the scope in which the symbol is created is chosen for us).
If, for example, class { !someValue = 123 }
did not expose the symbol in the outer scope, then it seems that there would be almost no practical difference between that and #private
fields, and in that case it also seems that this.!someValue
would essentially be syntax for inside the class/object definition just like #
.
Would there be any difference that would be useful over private fields if the symbol were not to be exposed in outer scope? If only the internal implementation mechanic differed, but otherwise the DX for end developers was merely a different symbol (! vs #), it wouldn't seem worth having.
The advantage of exposing the symbol in the outer scope is that it can be used just like a symbol manually declared would be usable (share it with other code in different ways).
Advantages over private fields
Plus symbols have advantages over private fields, which if we had .!
syntax would make them as easy to use syntactically as private fields, plus the optional benefits (for example, share a symbol to implement a loose protected methods that can be extended from class to subclass, not doable with pricate methods.
For example super.!someMethod()
with a shared symbol would work just as with any regular property, the look up happening on the prototype chain.