How to Implement the syntax features of Private Members and the implementation method of private members based on JavaScript, javascript syntax
Preface
Encapsulation is an essential concept in the object-oriented programming paradigm. In traditional object-oriented languages such as Java and C ++, private Members are an important way to implement encapsulation. However, JavaScript does not provide support for private members in terms of syntax features, which also enables developers to implement private members in JavaScript with various tricks, the following describes several methods for implementing the characteristics of private JS members and their advantages and disadvantages.
Some existing implementation solutions
Naming Conventions
The name of a member starting with '_' is specified as a private member. Only the class member method is allowed to be called, and private members are not allowed to be accessed from outside. The simple code is as follows:
JavaScript
Var MyClass = function () {this. _ privateProp = 'privateprop';}; MyClass. prototype. getPrivateProp = function () {return this. _ privateProp;}; var my = new MyClass (); alert (my. getPrivateProp (); // 'privateprop'; alert (my. _ privateProp); // It is not actually hidden. 'privateprop' is still displayed'
Advantages
There is no doubt that naming conventions are the simplest private member implementation solution and there is no work at the code level.
Debugging is convenient. You can directly view the Private Members of the object on the console to facilitate troubleshooting.
Good compatibility, supported by ie6 +
Insufficient
External access and changes to Private Members cannot be prevented. If a developer who does not know or does not comply with the Conventions changes the private attributes, nothing can be done.
We must force or persuade everyone to abide by this Agreement. Of course, this is not a big problem for teams with code specifications.
Es6 symbol Solution
In es6, a Symbol feature is introduced to implement private members.
The main idea is to generate a random and unique string key for the name of each private member. This key is invisible to the outside, and the internal visibility is achieved through the closure variable of js, the sample code is as follows:
JavaScript
(Function () {var privateProp = Symbol (); // each call generates a unique key function MyClass () {this [privateProp] = 'privateprop '; // This key} MyClass is used in the closure. prototype. getPrivateProp = function () {return this [privateProp] ;};}) (); var my = new MyClass (); alert (my. getPrivateProp (); // 'privateprop'; alert (my. privateProp); // The undefined dialog box is displayed, because the Member key is actually a random string.
Advantages
It makes up for the shortcomings of the naming convention scheme, and the external cannot obtain the access permission of Private Members through normal channels.
Debugging is acceptable to a certain extent. Generally, a string parameter is input to the constructor of symbol, and the private attribute name corresponding to the constructor on the console is displayed as: Symbol (key)
The compatibility is good, and it is easy for the shim to come out without the support of Symbol browsers.
Insufficient
The writing method is slightly awkward. You must create a closure variable for each private member so that internal methods can access it.
You can also use Object. getOwnPropertySymbols to obtain the symbol attribute name of the instance and obtain the access permission of Private Members. This kind of scenario rarely appears, and the level of developers who know this approach is confident that they have enough ability to know what impact their behavior will have, therefore, this deficiency is not really a deficiency.
Es6 WeakMap Solution
The Map and WeakMap containers are introduced in es6. The biggest feature is that the container's key name can be any data type. Although the original intention is not to implement private member introduction, however, it can be used unexpectedly to implement the private member feature.
The main idea is to create a WeakMap container at the class level to store private members of each instance. This container is invisible to the outside and visible to the inside through the closure; the internal method uses the instance as the key name to obtain the private member of the corresponding instance on the container. The sample code is as follows:
JavaScript
(Function () {var privateStore = new WeakMap (); // Private member storage container function MyClass () {privateStore. set (this, {privateProp: 'privateprop'}); // privateStore is used in the closure. Use the current instance as the key and set the private member} MyClass. prototype. getPrivateProp = function () {return privateStore. get (this ). privateProp ;};}) (); var my = new MyClass (); alert (my. getPrivateProp (); // 'privateprop'; alert (my. privateProp); // The undefined dialog box is displayed. The instance does not have the privateProp attribute.
Advantages
It makes up for the shortcomings of the naming convention scheme, and the external cannot obtain the access permission of Private Members through normal channels.
Encapsulate WeakMap and extract an implementation module with private features. You can write the program more concise and clean than the Symbol solution. For details about the implementation of one of the packages, refer to Article 3.
The last one is what I personally think is the biggest advantage: Based on the WeakMap solution, we can easily implement Member protection features (this topic will be discussed in other articles :))
Insufficient
Debugging is not good, because private members are all in the closure container, you cannot print instances on the console to view the corresponding private members.
For the performance issues to be confirmed, according to The es6 related email list, weakmap seems to locate the key in order by one-to-one comparison. The time complexity is O (n ), it is much slower than the hash algorithm's O (1 ).
The biggest defect is the memory expansion problem caused by compatibility. WeakMap's weak reference feature cannot be implemented in browsers that do not support WeakMap, so instances cannot be reclaimed. For example, in the sample code, privateProp is a large data item and cannot be recycled without weak references, resulting in Memory leakage.
Summary of existing implementation solutions
From the above comparison, the biggest advantage of the Symbol solution is that it is easy to simulate implementation, while the advantage of WeakMap is that it can implement protection for members, memory problems that cannot be tolerated at this stage are caused by the inability to simulate the implementation of weak reference features. As a result, my thinking turned to combining the two advantages.
Integration Scheme of Symbol + WeakMap class
The biggest problem in WeakMap solution is that it cannot be referenced by shim, and the secondary problem is that it is not convenient for debugging.
The WeakMap generated by shim is mainly unable to trace the life cycle of the instance, and the life cycle of Private Members on the instance is dependent on the instance. So it is not good to put the Private Members on the instance? The instance is gone, and its properties are naturally destroyed. You can use Symol to hide the private storage area.
This solution provides a createPrivate function, which returns a private token function that is invisible to the outside. It is obtained through the closure function internally and transmitted to the current instance to return the private storage region of the current instance. The usage is as follows:
JavaScript
(Function () {var $ private = createPrivate (); // the token function of a private member. The object parameter can be passed in as the private member function MyClass () of the prototype chain () {$ private (this ). privateProp = 'privateprop'; // privateStore is used in the closure. Use the current instance as the key and set the private member} MyClass. prototype. getPrivateProp = function () {return $ private (this ). privateProp ;};}) (); var my = new MyClass (); alert (my. getPrivateProp (); // 'privateprop'; alert (my. privateProp); // The undefined dialog box is displayed. The instance does not have the privateProp attribute.
The Code mainly implements the createPrivate function, which is roughly implemented as follows:
JavaScript
// createPrivate.jsfunction createPrivate(prototype) { var privateStore = Symbol('privateStore'); var classToken = Symbol(‘classToken'); return function getPrivate(instance) { if (!instance.hasOwnProperty(privateStore)) { instance[privateStore] = {}; } var store = instance[classToken]; store[token] = store[token] || Object.create(prototype || {}); return store[token]; };}
The preceding Implementation implements two layers of storage. The privateStore layer is the unified private member storage area on the instance, while the classicen layer corresponds to the private member definitions of different classes between the inheritance layers, the base class has the private member area of the base class. The private member areas of the subclass and the base class are different.
Of course, only one layer of storage can be implemented. Two layers of storage are only for debugging convenience. You can directly view the private part of an instance at each layer through the Symbol ('privatestore') attribute on the console.
Wonderful es5 property getter interception Solution
This solution is purely idle and boring. It mainly uses the getter provided by es5, according to argument. callee. caller determines the call scenario. If it is an external call, an exception is thrown or undefined is returned. If it is an internal call, real private members are returned, which is complicated to implement and does not support the strict mode, it is not recommended. If you are interested, you can check the implementation.
Summary
Compared with the above schemes, I personally prefer the integration scheme of Symbol + WeakMap, which combines the advantages of the two and makes up for the shortcomings of WeakMap and the redundancy of Symbol writing. Of course, I believe that with the development of JS, Private Members and protection Members will support it at the syntax level sooner or later, just as es6 supports class keywords and super syntax sugar, at this stage, developers need to use some skills to fill the gaps in language features.
Implementation of Javascript Private Members
In general, this book is still acceptable, but after reading this book, there are still a few issues that have plagued me, such as the implementation of private variables in js and prototype. After a series of tests, now I understand it.
Many books have said that Javascript cannot truly implement Javascript Private Members. Therefore, during development, a unified convention _ starts with a private variable with two underscores.
Later, we found the closure feature in Javascript, which completely solved the Javascript private member problem.
Function testFn () {var _ Name; // defines the Javascript private member this. setName = function (name) {_ Name = name; // get _ Name} this from the current execution environment. getName = function () {return _ Name ;}// End testFn var test = testFn (); alert (typeof test. _ Name = "undefined") // true test. setName ("KenChen ");
Test. _ Name cannot be accessed at all, but it can be accessed using the object method, because the closure can obtain information from the current execution environment.
Next, let's take a look at how the common members are implemented.
function testFn(name){ this.Name = name; this.getName = function(){ return this.Name; } } var test = new testFn("KenChen"); test.getName(); //KenChen test.Name = "CC"; est.getName();//CC
Next, let's take a look at how static variables are implemented.
function testFn(){ } testFn.Name = "KenChen"; alert(testFn.Name);//KenChen testFn.Name = "CC"; alert(testFn.Name);//CC