What is a private property

A private property is a very common feature in OOP. It generally means that it can be accessed by different methods inside the class, but not outside the class. Most languages implement access control through public, private, and protected access modifiers.

The significance of private attributes (methods) is largely to hide the internal implementation of the class, while external interfaces are exposed only through public members to reduce external dependence on or modification of the internal implementation of the class.

To put it simply, external callers only care about parts of a class and do not use internal variables or methods. If I expose everything to the outside world, on the one hand, it increases the understanding and acceptance costs of the user. On the other hand, it also increases the maintenance cost of the class itself and the risk of external damage.

Therefore, exposure and user interaction interface, only don’t interact with other part of the hidden private, can promote the reliability of the system, (to prevent external pig teammates crazy to modify your class), can also reduce the user’s information load (I only need to call a method to get what I want, I don’t care other).

Private properties in Js

As we all know, there are no public, private, protected access modifiers in JavaScript, and for a long time there was no concept of private attributes. Objects’ attributes/methods are public by default.

This means that if you write a function or class, the outside can be accessed and modified at will, so you can see a lot of hack writing in JS. If you change a value from the outside, the logic inside will change. This itself is a very dangerous thing, at the same time for a development, how can allow, so through the logic and data for a certain encapsulation and magic change, JS developers are on the curve to achieve the “private attribute” road.

Self-deception type

Self-deception type, I say he is private, then he is private, do not allow refutation. Or by convention, by some sort of unwritten convention, prefixing a variable with the underscore “_”, that it’s a private property; But in fact, this class of attributes is no different from normal attributes, you can still access it externally, just because you see the prefix when you access it, oh, this is a private, I shouldn’t access it directly.

class Person {
  _name;
  constructor(name) {
    this._name = name;
  }
Copy the code

In theory, the same applies to ts’s private modifiers. Although TS implements private, public, and other access modifiers, they are checked only at compile time, and the resulting access control is not implemented at runtime.

closure

Closure is the idea that in JavaScript, an inner function can always access the parameters and variables declared in its outer function, even after its outer function has been returned (end-of-life).

Based on this feature, we can simulate implementing a private variable by creating a closure

var Person = function(name){
    var _name = name;
    this.getName = function(){
        return _name;
    };
    this.setName = function(str){
        _name = str;
    };
};
var person = new Person('hugh');

person.name;        // undefined, name is a variable in the Person function
person.getName();   // 'hugh'
person.setName('test'); 
Copy the code

Or class

class Person {
  constructor(name) {
    // Private attributes
    let _name = name; 
    this.setName = function (name) {
      _name = name;
    };
    this.getName = function () {
      return _name;
    };
  }

  greet() {
    console.log(`hi, i'm The ${this.getName()}`); }}Copy the code

You need to define getters and setters for each variable. Otherwise, you can’t even get the entire private variable inside the class. But when you define a getter, This getter can also be used externally to get private variables.

So closures make it so that you can’t read the internal private property directly, and you can’t use the private property directly inside the class either.

Symbol and weakMap

We can know that the realization of private attributes, as long as the external can not know the attribute name, only the internal known attribute name, can achieve external access to the characteristics, based on ES6 new syntax symbol and weakMap, we can achieve this ability.

Based on the symbol

Symbol is a new primitive data type introduced in ES6 that represents unique values and can be called attribute names of objects. An attribute name that is completely unique and cannot be explicitly expressed except by retrieving it directly from a variable. Perfect.

var Person = (function(){
    const _name = Symbol('name');
    class  Person {
        constructor(name){
            this[_name] = name;
        }
        get name() {return this[_name]
        }
    }
    return Person
}())

let person = new Person('hugh');
person.name  // hugh
Copy the code

Based on the WeakMap

WeakMap, like Symbol, is a map that uses the object as the key, so we can use the instance itself as the key

var Person = (function(){
    const _name = new WeakMap(a);class  Person {
        constructor(name){
            _name.set(this, name)
        }
        get name() {return _name.get(this)}}return Person
}())

let person = new Person('hugh');
person.name  // hugh
Copy the code

The class proposal

The good news is that ES2019 has added native support for class private properties, which can be made private by adding a ‘#’ in front of the property/method name, and support for defining private static properties/methods. It is also now available via Babel (which compiles # to weakMap to implement private properties), and Node V12 has added support for private properties.

class Person {
  // Private attributes
  #name; 

  constructor(name) {
    this.#name = name;
  }
	get name() {return this.#name; }}Copy the code

As to why is #, rather than the commonly used private modifier, you can see this article zhuanlan.zhihu.com/p/47166400

Read-only property

A read-only property is similar to a private variable in that logically you just add a getter to your private property and don’t add a setter and it’s a read-only property.

class Person {
  constructor(name) {
    // Private attributes
    let _name = name; 
    this.name = function () {
      return_name; }; }}Copy the code

The tricky part is that you have to use getter methods to get properties, but of course we can simplify this by using class get

class Person {
  // Private attributes
  #name; 

  constructor(name) {
    this.#name = name;
  }
	get name() {return this.#name; }}Copy the code

While this is a perfect read-only property for simple types, for complex types like objects and arrays, you can still add properties externally.

class Person {
  // Private attributes
  #name; 

  constructor() {
    this.#name = {};
  }
	get name() {return this.#name; }}let person  = new Person();
person.name.title = 'hugh';
person.name // {title:'hugh'}
Copy the code

To make the property of the object type immutable, we can freeze the property

The value of an existing property in an Object that is frozen with object.freeze () is immutable, cannot be edited, and cannot be added. An Object sealed with object.seal () can change its existing property values, but cannot add new ones.

class Person {
  // Private attributes
  #name; 

  constructor() {
    this.#name = {title:'hugh'};
		Object.freeze(this.#name)
  }
	get name() {return this.#name; }}Copy the code

The problem with the freeze property is that you can’t change it inside the class either, so if you want to read it externally, but there is a way to change the value, you can’t use freeze.

Object. DefineProperty and proxy

To set an object’s value readable, we can use defineProperty to set its writable to false in a simpler way

var obj = {};
Object.defineProperty( obj, "< attribute name >", {
  value: "< attribute value >".writable: false
});
Copy the code

There are, of course, significant limitations:

  1. There is no way to prevent the entire object from being replaced, i.e. obj can be assigned directly
  2. It needs to be set for each property of the object, and does not apply to new properties (unless you call it when you add it).
  3. Nested objects also do not prevent internal editing changes.

For this we can use es6 proxy to optimize, proxy can achieve most of the functions of defineProperty without the above problems

var obj = {};
const objProxy = new Proxy(obj, {
    get(target,propKey,receiver) {
      return Reflect.get(target, propKey, receiver);
    },
    set() { // Intercepts write property operations
      console.error('obj is not writeable');
      return true; }});Copy the code

At this point, we don’t need to worry about new properties inside OBJ (although, for nested objects, there’s still nothing to stop it)

Based on the above scenario, we can optimize the read-only property from the beginning

class Person {
  // Private attributes
  #name; 

  constructor() {
    this.#name = {};
  }
	get name() {return new Proxy(this.#name, {
		    get(target,propKey,receiver) {
		      return Reflect.get(target, propKey, receiver);
		    },
		    set() { // Intercepts write property operations
		      console.error('obj is not writeable');
		      return true; }}); }addName(name){
		this.#name[name] = name; }}let person  = new Person();
person.name.title = 'hugh'; // obj is not writeable
person.addName('hugh')
Copy the code

For compatibility with proxy, we can introduce Google proxy-Polyfill. However, it should be noted that proxy-polyfill must be the attribute of a known object because it needs to traverse all attributes of the object and set defineProperty for each attribute. Proxy-pollyfill Seal prevents you from adding new attributes by using target and proxy. See also:

Github.com/GoogleChrom…