A class is a template for creating objects, consisting of a set of properties and methods that represent data and operations on the same concept.

Some properties and methods are external, but some are just for internal use, that is, private. How do you implement private properties and methods?

I don’t know how you would do it, but I’ve combed through it, and I’ve done it in about six ways. Let’s take a look at each one:

_prop

The easiest way to distinguish between private and common is to put an underscore underscore, just by naming it.

Such as:

class Dong {
    constructor() {
        this._name = 'dong';
        this._age = 20;
        this.friend = 'guang';
    }

    hello() {
        return 'I\'m ' + this._name + ', '  + this._age + ' years old'; }}const dong = new Dong();

console.log(dong.hello());
Copy the code

The Dong has private _name, _age, and a shared friend.

However, this method is just a naming convention, which tells the developer that the property or method is private and should not be called, but it is not mandatory and cannot be prevented if others want to use it.

However, this way is used quite a lot, a long history.

So how can you achieve true private ownership based on this specification? This is where the Proxy comes in:

Proxy

The Proxy can define the get, set, and object. keys logic of the target Object.

For example, this class:

class Dong {
    constructor() {
        this._name = 'dong';
        this._age = 20;
        this.friend = 'guang';
    }

    hello() {
        return 'I\'m ' + this._name + ', '  + this._age + ' years old'; }}const dong = new Dong();
Copy the code

Instead of calling its object property methods directly, we use a layer of Proxy to constrain the behavior of get, set, and getKeys:

const dong = new Dong();
 
const handler = {
    get(target, prop) {
        if (prop.startsWith('_')) {
            return;
        }
        return target[prop];
   },
   set(target, prop, value) {
    if (prop.startsWith('_')) {
        return;
     }
     target[prop] = value;
   },
   ownKeys(target, prop) {
      return Object.keys(target).filter(key= >! key.startsWith('_'))}},const proxy = new Proxy(dong, handler)
Copy the code

Get, set, and ownKeys handlers for dong are defined using new Proxy:

  • Get: Return null if it begins with underscore _, otherwise return the target object property value target[prop]
  • Set: Returns if it begins with underscore _, otherwise sets the property value of the target object
  • OwnKeys: When accessing keys, filter out properties beginning with an underscore in the target object and return

This privates the attributes that begin with an underscore:

Let’s test it out:

const proxy = new Proxy(dong, handler)

for (const key of Object.keys(proxy)) {
    console.log(key, proxy[key])
}
Copy the code

Indeed, only methods with common attributes are printed, and the two attributes beginning with the underscore are not.

We implemented real private properties based on the _prop naming convention!

Call the method again:

Why is undefined?

Since proxy.hello’s this also refers to the proxy and is restricted, we need to do something else:

If the method is used, bind this to it as the target object.

This allows the Hello method to access private attributes starting with _ :

We implement real private attributes with the Proxy naming convention for underscores, but defining a layer of proxies is a bit tricky. Is there a way not to define Prxoy?

B: Yes, we do.

Symbol

Symbol is an API added to ES2015 to create unique values. Based on this unique property, we can implement private properties.

Like this:

const nameSymbol = Symbol('name');
const ageSymbol = Symbol('age');

class Dong {
    constructor() {
        this[nameSymbol] = 'dong';
        this[ageSymbol] = 20;
    }

    hello() {
        return 'I\'m ' + this[nameSymbol] + ', '  + this[ageSymbol] + ' years old'; }}const dong = new Dong();

Copy the code

Instead of using name and age as private property names, we use Symbol to generate unique values for names.

If you can’t get the property name, you can’t get the corresponding property value:

This way is simpler than the Proxy way, but also a lot of use of a way to achieve private attributes.

If you want to expose it, you can define a get method:

But is this private property really inaccessible?

No, there is an API called Object. GetOwnPropertySymbols, can take all the Symbols properties to the Object, and then you can get the attribute value:

So this method is not as complete as the Proxy method, but the Object. Keys can not get the corresponding attributes.

Is the method without Proxy more perfect than that with or without Symbol?

Try this:

WeakMap

Properties and methods can be accessed externally because we’ve attached them to this, so if we don’t attach them to this, we can’t access them externally.

For example, using a Map to hold private attributes:

const privateFields = new Map(a);class Dong {
    constructor() {
        privateFields.set('name'.'dong');
        privateFields.set('age'.20);
    }

    hello() {
        return 'I\'m ' + privateFields.get('name') + ', '  + privateFields.get('name') + ' years old'; }}Copy the code

Let’s test it out:

That seems to work, but I don’t know if you’ve noticed the problem:

  • All objects use the same Map and interact with each other
  • Object is destroyed and the Map still exists

How to solve this problem?

I don’t know if you have used WeakMap before. Its characteristic is that it can only use objects as keys. When objects are destroyed, the key value pairs are destroyed.

Perfect solution to the above two problems:

  • Since objects are used as keys, different objects are placed on different key-value pairs and have no effect on each other
  • When an object is destroyed, the corresponding key-value pair is destroyed without manual management

It seems to be perfect. Let’s implement:

const dongName = new WeakMap(a);const dongAge = new WeakMap(a);const classPrivateFieldSet = function(receiver, state, value) {
    state.set(receiver, value);
}

const classPrivateFieldGet = function(receiver, state) {
    return state.get(receiver);
}


class Dong {
    constructor() {
        dongName.set(this.void 0);
        dongAge.set(this.void 0);

        classPrivateFieldSet(this, dongName, 'dong');
        classPrivateFieldSet(this, dongAge, 20);
    }

    hello() {
        return 'I\'m ' + classPrivateFieldGet(this, dongName) + ', '  + classPrivateFieldGet(this, dongAge) + ' years old'; }}Copy the code

Each property defines a WeakMap to maintain, key is the current object, value is the property value, get and set use classPrivateFieldSet and classPrivateFieldGet. This is eventually accessed from WeakMap.

Dongname. set(this, void 0); void 0 returns undefined.

Under test:

Wow, private properties can also be implemented with WeakMap!

ClassPrivateFieldGet (xxmap.get)

Indeed, the purpose of wrapping a layer is to add some additional logic, which can also be taken directly from weakMap.

But that’s a lot of trouble to write. Is there an easier way?

Could you design a syntactic sugar that automatically compiles in this way?

Yes, there are grammatical sweets:

#prop

There is now an ES draft for private properties that identifies private properties and methods with #.

Like this:

class Dong {
    constructor() {
        this.#name = 'dong';
        this.#age = 20;
        this.friend = 'guang';
    }
    hello() {
        return 'I\'m ' + this.#name + this.#age + 'years old'; }}Copy the code

Both name and age are private, while friend is common.

This new syntax is not supported as quickly by the JS engine, but can be used ahead of time by compiling into lower versions of the syntax with Babel or TS compiler.

For example, Babel has the @babel/proposal-private-property-in-object plugin, which can compile this syntax:

Babel does this by compiling #prop into WeakMap.

This plugin, in the @babel/preset-env preset, automatically introduces:

In addition to Babel, ts can also use this syntax directly:

Will also be compiled into WeakMap way to achieve.

In fact, TS implementation of the new syntax is quite a few, such as? And???? The syntax for optional chains and default values is equivalent:

constres = data? .name ??'dong';
const res2 = data && data.name  || 'dong';
Copy the code

This new syntax is directly available, Babel needs to introduce a proposal plugin.

Ts class has a private modifier, too.

It’s actually private but not quite, so let’s see:

ts private

Ts can use private to modify the visibility of properties and methods:

  • Private indicates that the property is private and accessible only within the class
  • Protected means protected. Only class and subclass are accessible
  • Public indicates the shared property and can be accessed externally

There is a difference between type checking and prompting, such as the private property is not accessible outside the class:

The inside of class is accessible:

But this constraint is only for type checking, it only exists at compile time, not at run time.

We can look at the compiled code:

You can see that nothing is being done.

In addition to being private at compile time, the runtime is also private with #prop:

So, to implement real private, use #prop, and declare private if it’s just a compile-time constraint.

conclusion

Class is used to define a set of properties and methods, both internal and external, around a concept. Only properties and methods for internal use need to be privatized.

I set up six ways to implement private property methods:

  • It is nominally distinguished by the underscore _prop
  • Define the logic of get, set, and ownKeys through Proxy
  • Use Symbol to define unique attribute names, not keys
  • Use WeakMap to hold private properties and methods for all objects
  • Private with #prop’s new ES syntax, Babel and TSC will compile them to WeakMap
  • Constraints at compile time via TS private

Of the six methods, three are only pseudo-private, such as _prop (still accessible), TS private (accessible at runtime), and Symbol (accessible via Object. GetOwnSymbols).

The other three are truly private, including Proxy, WeakMap, and # Prop (currently the way to compile to WeakMap).

How many of these 6 ways to implement private properties have you used?