Class field declarations for JavaScript (a field declaration for JavaScript classes) has now entered Stage-3, which includes a focus for OOP developers: Private fields. JavaScript has never had a private member for no reason, so this proposal poses new challenges for JavaScript. But at the same time, JavaScript is already considering privatization at the time of ES2015 's release, so it is not without foundation to implement private members.
In the fourth chapter of the column "JavaScript full stack Engineer formation", the author discusses the key differences between prototype OOP and inheriting OOP. Here's a look at the problem with JavaScript private members today.
Keng
First dig a hole-this is a JS code, BusinessView
in order to do two things, that is, the layout of forms and maps.
Represents the _
prefix convention as private
class BaseView { layout() { console.log("BaseView Layout"); }}class BusinessView extends BaseView { layout() { super.layout(); this._layoutForm(); this._layoutMap(); } _layoutForm() { // .... } _layoutMap() { // .... }}
Then, due to the development of the business, there are many views that have a map layout. This is accomplished by inheriting, so the BusinessView
map-related content is abstracted from a base class called MapView
:
class MapView extends BaseView { layout() { super.layout(); this._layoutMap(); } _layoutMap() { console.log("MapView layout map"); }}class BusinessView extends MapView { layout() { super.layout(); this._layoutForm(); this._layoutMap(); } _layoutForm() { // .... } _layoutMap() { console.log("BusinessView layout map"); }}
The above two pieces of code are very typical based on the idea of OOP, the intention is to expect all levels of the class can be layout()
carried out at all levels should be responsible for the layout of the task. But there is always a gap between ideal and reality, and running in JavaScript will find that it BusinessView._layoutMap()
was executed two times without MapView._layoutMap()
execution. why not?
Virtual functions
In JavaScript, this method in the descendant class is called by default if the same name is defined in the ancestor and descendant classes. If you want to invoke a method of the same name in an ancestor class, you need to invoke it through the descendant class super.
.
Here you can analyze the process:
When a subclass creates an object, its class and all ancestor class definitions are loaded. This time
- Call
BusinessView.layout()
- Find
super.layout()
, start callingMapView.layout()
MapView.layout()
Called inthis._layoutMap()
- Then look for the current object (
BusinessView
object)_layoutMap()
- Find, call it
You see, because of the BusinessView
definition _layoutMap
, we don't even have to search the prototype chain. Right, this is the limitation of OOP based on the prototype relationship. If we look at the process of C #, we'll see a different
- Call
BusinessView.layout()
- Find
base.layout()
, start callingMapView.layout()
MapView.layout()
Called inthis._layoutMap()
MapView
found in_layoutMap()
- Check whether virtual functions
- If yes, the last overloaded (override) function is found on the subclass, calling
- If not, call directly
Did you find the difference? The key is to judge the "virtual function".
But what does this have to do with private members? Because private functions are definitely not virtual functions, in C #, if _layoutMap
they are defined as private, then the MapView.layout()
call must be MapView._layoutMap()
.
The concept of virtual functions is a little complicated. It can be simply understood, however, that if a member method is declared as a virtual function, it will be called at the time it is called with its virtual function chain to find the last overload.
In JavaScript, although _
the contract prefix is private, it is only a gentleman's covenant, which is still not private in essence. The gentleman's covenant is effective to the person, the computer does not know you have this agreement .... However, if JavaScript really implements a private member, then the computer knows that it _layoutMap()
is a private method and should invoke the definition in this class instead of looking for the definition in the subclass.
Solving the current privatization problem
JavaScript does not have a private member at the moment, but we need to solve the problem of private members effectively. Of course there are ways to use Symbol
and closure to solve.
Note that the closure here is not a guide to generating closures in function functions, keep looking down
First, let's make it clear that we have a flexible view of the privatization issue--that is, the ancestor class caller does not go to the subclass to find it first when invoking a method. This problem can not be solved syntactically, JavaScript is to from the concrete instance from the back to the search for the specified name method. But what if the method name is not found?
It can be found because the method name is a string. A string represents the same meaning within the global scope. But ES2015 brought Symbol
it, it had to be instantiated, and each instantiation must represent a different identity--if we define the class in a closure, declare one in the closure, Symbol
use it as the name of the private member, the problem is solved, such as
const MapView = (() => { const _layoutMap = Symbol(); return class MapView extends BaseView { layout() { super.layout(); this[_layoutMap](); } [_layoutMap]() { console.log("MapView layout map"); } }})();const BusinessView = (() => { const _layoutForm = Symbol(); const _layoutMap = Symbol(); return class BusinessView extends MapView { layout() { super.layout(); this[_layoutForm](); this[_layoutMap](); } [_layoutForm]() { // .... } [_layoutMap]() { console.log("BusinessView layout map"); } }})();
Modern module-based definitions, even closures, can be saved (the module system automatically closes the scope)
const _layoutMap = Symbol();export class MapView extends BaseView { layout() { super.layout(); this[_layoutMap](); } [_layoutMap]() { console.log("MapView layout map"); }}
const _layoutForm = Symbol();const _layoutMap = Symbol();export class BusinessView extends MapView { layout() { super.layout(); this[_layoutForm](); this[_layoutMap](); } [_layoutForm]() { // .... } [_layoutMap]() { console.log("BusinessView layout map"); }}
The post-reform code can be output as expected:
BaseView LayoutMapView layout mapBusinessView layout map
Postscript
The author has developed a series of thinking habits of analyzing and solving problems in the course of many years ' development, so it is often possible to quickly penetrate the phenomena to see the substantive problems to be solved, and to solve them based on the existing conditions. Indeed, Symbol
one of the reasons for this is to solve the problem of privatization, but why and how it needs to be analyzed and thought out.
Learning can get people to solve the same problems, but thinking can help people solve similar problems. Readers are welcome to study the author's column, "JavaScript All-stack engineer in mind", and follow the author to think, analyze and solve some problems in the software development process.
JavaScript Private Members