preface

In object-oriented languages, we’ve all heard and used the concept of private variables a lot. For example, when defining a member of a class in Java, you simply prefix the member with the private keyword. However, implementing this concept in JavaScript is not so easy and requires a few “tricks”. The following is a description of JavaScript private variables in JavaScript Advanced Programming:

Strictly speaking, JavaScript has no concept of private members; all attributes are public. However, there is a concept of private variables. Any variable defined in a function or block is considered private because it cannot be accessed from outside the function or block.

Therefore, we can see that this little trick refers to closures, and it is primarily a matter of scope

implementation

There are several ways to implement JavaScript private variables. Here are some common ones:

convention

This is the simplest way to implement it. You only need to specify which members are private before development, such as variables that start with _, and then do not directly access or modify those members

 class Person {
     constructor(age) {
         this._age = age
     }
     
     getAge() {
         return this._age
     }
     
     setAge(newAge) {
         this._age = newAge
     }
 }
 ​
 // The convention does not use p._age to access and modify members
 let p = new Person(21)
Copy the code

This method has many disadvantages, generally not used, here is just a brief mention

Privilege method

Create a private variable inside the class, and then create a public method that can access the private variable.

 class MyObject {
     constructor() {
         let value = 123
         
         this.getValue = () = > {
             return value
         }
         
         this.setValue = (newValue) = > {
             value = newValue
         }
     }
 }
 ​
 const o = new MyObject()
 ​
 o.getValue()
 / / 123
 o.value
 // undefined
 ​
 o.setValue(233)
 ​
 o.getValue()
 / / 233
Copy the code

As can be seen from the printed results of the above code, the value on the instance cannot be accessed directly, and the privileged methods getValue and setValue can be used to operate on the value

Value is not accessible here because it is just a variable declared in the constructor. It is not bound to this or added to the stereotype chain. It can only be accessed in the constructor’s scope. The two privileged methods we’ve written are essentially a closure that references the scope of the constructor and therefore access value

The downside of this approach is that privileged methods cannot be reused and a new function must be created each time an instance is created. Static private variables are a good way to avoid this problem

Static private variable

To address the reuse of privileged methods, we need to add them to the prototype of the class, not to the instance. To achieve this effect, it can’t be in the constructor to declare private variables, but in the constructor declared outside, but not outside the scope of the pollution, so you need to use an immediate execution function to build a private scope, then in this private scope statement private variables and class, the code is as follows:

 const MyObject = (function() {
     let value = 123
 ​
     class MyObject {
         constructor() {
             // ...
         }
 ​
         getValue() {
             return value
         }
 ​
         setValue(newValue) {
             value = newValue
         }
     }
 ​
     return MyObject
 })()
 ​
 const o = new MyObject()
 const o2 = new MyObject()
 ​
 o2.getValue === o.getValue
 // true
 ​
 o.getValue()
 / / 123
 o.value
 // undefined
 ​
 o.setValue(233)
 ​
 o.getValue()
 / / 233
 o2.getValue()
 / / 233
Copy the code

As you can see from the above code and how it works, privileged functions created in this way can be effectively reused and privatized. Note that because a private variable is declared outside the constructor, it is shared between all instances, meaning that it is a static variable

Because of this feature, we need to determine whether to use static or in-instance private variables according to our own requirements during development

Symbol

There is also a method that allows each instance to have its own private variable and the privileged method to be reused. This method uses symbol, a new data type introduced in ES6, to create variables of that type using the symbol () function

This type has the characteristic that each Symbol value returned from the Symbol() function is unique and can be used as an identifier for object properties, making it an excellent choice for implementing class private variables

A more detailed description of Symbol can be found in the MDN documentation here

 const MyObject = (function() {
     let _value = Symbol('value')
     let _value2 = Symbol('value')
     _value == _value2   // false
     
     class MyObject {
         constructor() {
             this[_value] = 123
         }
         
         getValue() {
             return this[_value]
         }
 ​
         setValue(newValue) {
             this[_value] = newValue
         }
     }
     
     return MyObject
 })()
 ​
 const o = new MyObject()
 ​
 o[Symbol('value')]
 // undefined
 ​
 o.getValue()
 / / 123
Copy the code

The _value, as an object attribute identifier, can be used like a string, so private variables can be read and written through the this pointer and, because of the nature of the symbol type, cannot be read by outsiders. At the same time, because privileged methods are defined on the prototype, function reuse is also achieved

The object structure created in this way is as follows:

Private variables implemented in this way are already as good as any other object-oriented language, so I personally use it as a best practice

The module pattern

While the previous approaches create private variables and privileged methods through custom types, the modular pattern, proposed by Douglas Crockford, implements the same isolation and encapsulation on a singleton

 const singleton = function() {
     let value = 123
     
     return {
         // Other public attributes
         getValue() {
             return value
         },
         setValue(newValue) {
             value = newValue
         }
     }
 }()
Copy the code

Using an anonymous function to provide a private scope and return an object with only public members, this approach is suitable for scenarios where you don’t need to create types, but instead focus on objects, and is truly “object-oriented” (a bit like old-style inheritance in inheritance).

You can also enhance a new object before it is returned, which is appropriate for scenarios where the returned singleton needs to be an instance of a particular type and additional properties or methods must be added to it, as in this example:

 const singletono = function() {
     let value = 123
     
     const object = new SomeType()
     
     object.publicProperty = true
     
     object.getValue = function() {
         return value
     }
     
     object.setValue = function(newValue) {
         value = newValue
     }
     
     return object
 }()
Copy the code

This returns an object that can be typed by the instanceof operator, with corresponding private variables and privileged methods that operate on private variables (sort of like parasitic inheritance in inheritance).

ES proposal: Private class fields

All of these implementations are implemented in JavaScript without built-in private variables. However, in the ES proposal of JavaScript by Daniel Ehrenberg and Jeff Morrison, You can use the # symbol to indicate a private variable directly in a JS class, and you also need to use the # symbol to access the variable, as in this example:

class MyObject {
     #value = 123

     getValue() {
         return this.#value
     }

     setValue(newValue) {
         this.#value = newValue
     }
 }
 
 const o = new MyObject()
 
 o.#value
 // Uncaught SyntaxError: Private field '#value' must be declared in an enclosing class
 
 o.getValue()
 / / 123
Copy the code

This approach looks just like a convention, except that it is provided by the specification and is consistent

If you’re interested in using the # symbol instead of using the private keyword for a private variable, as other object-oriented languages do, click here

conclusion

To realize private variables, in my mind, the best solution is: use symbol implementation, can solve most of the use of private variables in the scene. And other ways (in addition to the convention), you can choose according to their own needs

After being reminded by @bigint in the comment section, # in the new proposal was added. After learning that the proposal entered phase 3 in July 2017, AND I tested it a little, the above code use case can run normally on Chrome, Edge and FireFox. It’s basically safe to use (IE? What’s that?