📜 ⬆️ ⬇️

Implementing Private Fields with WeakMap in JavaScript

Prehistory


One of the biggest omissions in JavaScript is the impossibility of creating private fields in user types. There is only one good way to create a private variable inside the constructor and create privileged methods that will have access to them, for example:

function Person(name) { this.getName = function() { return name; }; } 

In this example, the getName () method uses the name argument (which is in fact a local variable) to return the value of the person’s name without expanding the name as a property of the object. This approach is quite suitable for itself, but very inefficient in terms of performance. As you know, functions in JavaScript are objects and if you use more instances of a Person object, each will store its own copy of the getName () method, instead of using just one of the prototype.

As an alternative solution, you can choose the way to create a field private by agreement, making a prefix in its name, usually in the form of underscores. The underlining is not magic, it does not protect the field from use, rather just a reminder that it is not worth touching. For example:

 function Person(name) { this._name = name; } Person.prototype.getName = function() { return this._name; }; 

This pattern is more effective since each instance will use the same method from the prototype. The method, like the field, is accessible from the outside; all that we have done is agreed that you cannot touch ._name . This solution is far from ideal, but a rather large number of programmers use it. At one time it came to us from Python .
')
There is another option when we use a common field for all instances, which can be easily created using the IIFE function, which contains a constructor. For example:

 var Person = (function() { var sharedName; function Person(name) { sharedName = name; } Person.prototype.getName = function() { return sharedName; }; return Person; }()); 

Here, sharedName is common to all instances of Person , and each new instance will overwrite the value with the name argument. This is obviously not a meaningful solution, but very important in the way of understanding how we can implement truly private fields.

On the way to private fields


The shared private field pattern indicates a potential solution: what if the private data is not stored in the instance, but will have access to it? What if there is an object that can keep hidden fields away and hide all private information about the implementation? Before ECMAScript 6, you probably would have implemented it like this:

 var Person = (function() { var privateData = {}, privateId = 0; function Person(name) { Object.defineProperty(this, "_id", { value: privateId++ }); privateData[this._id] = { name: name }; } Person.prototype.getName = function() { return privateData[this._id].name; }; return Person; }()); 

In this way, we have come to something like this :) The privateData object is not accessible from outside IIFE , it completely hides all the information stored inside. The variable privateId stores the next available ID that the instance uses. Unfortunately, the ID has to be stored in the instance and you should make sure that it is also not available during use. Therefore, we use Object.defineProperty () to set the value and to make sure that the variable is read only. Then inside getName () , the method accesses private variables using _id , for reading and writing.

This approach is good, quite suitable for storing private variables. But the extra use of _id is only part of the trouble. This approach also causes problems - even if the instance is deleted by the garbage collector, the data that he wrote to privateData will remain in memory. Anyway, it is the best that we can implement in ECMAScript 5 .

WeakMap output


image
WeakMap solves the remaining problems of the previous example. First, we don’t have to store a unique ID anymore, since the instance itself can be a unique ID. Secondly, the garbage collector will be able to remove the record that we no longer need, since WeakMap is a collection with weak links. After the instance is deleted, the entry will not have hard links. The garbage collector in this case takes away from the life of all the records that were associated with this instance. Exactly the same basic pattern from the previous example, but more kosher:

 var Person = (function() { var privateData = new WeakMap(); function Person(name) { privateData.set(this, { name: name }); } Person.prototype.getName = function() { return privateData.get(this).name; }; return Person; }()); 

In this example, the privateData is an instance of WeakMap . When a new Person is created, a new record is added to WeakMap to store private variables. The key in WeakMap is this , although the developer can access an instance of a Person object, he cannot access privateData from outside this instance. Any method that wants to access this data simply transfers its instance (within which the method is located). In this example, getName () gets an entry and returns the name property.

Conclusion


This can be a great example for people who have not found the use of the new WeakMap object in ECMAScript 6 . Many predict future changes in the way we write JavaScript code. For me personally, this is a kind of a turning point in the power of OOP JavaScript .

Source: https://habr.com/ru/post/257305/


All Articles