Public var in javascript

Steve the Dev

Of the features that has always been painfully missing from JavaScript, one of the most impactful is the conspicuous inability to use the public , private , and protected keywords to explicitly state the access-controls of class members. I would imagine that this particular deficiency owes its origins to the same decisions that led to JavaScript not having a class keyword until ECMAScript 6 came about. Regardless of its origins, however, it’s a feature whose absence I’ve always lamented — and one which many developers have sought to emulate. Over the last couple of years, I’ve found that my favorite implementation is to use the built-in WeakMap object to give me the features I need. Although it’s not perfect, it does suit my needs in a way that other solutions do not. I’ll explain:

What do I mean when I say public, private, and protected scope?

If you’ve been programming for a while, then there is a pretty good chance that you’ve been exposed to these concepts before. If you are a little confused as to what scope means, then you may want to read more about it in my introduction to JavaScript’s implementation of Scope. The CliffsNotes?

  • A class member that has been declared public is available to everything that is able to access the class instance that owns the member.
  • A class member that has been declared private is only accessible from within the class that instantiated the object.
  • A class member that has been declared protected is only accessible by the object that owns the values.

If this sounds complicated, don’t worry. I’ll explain this in more detail.

Public Scope

I mentioned earlier that public scope is available to everything that is able to access a class’ instantiated object. If this sounds obvious, it’s because that’s the only way JavaScript does things. If you create an object in JavaScript, you can access all of its properties:

So long as you can access object , you can also access publicProperty and any other property that is attached in this way.

Private Scope

Unlike public scope, private scope is only accessible to the object that owns the scope. By convention, there are two ways that people typically accomplish this: conventional privacy, and closures.

Conventional Privacy takes the form of marking class members with some defining characteristic — often an underscore — to tell other programmers that they shouldn’t mess with it:

If you’re trying to prevent tampering, then this provides some obvious problems. Inserting an underscore doesn’t prevent code from reading or (more importantly) writing to variables they have no business modifying.

object._conventionallyPrivate = 'I do what I want! #hacker';

The most common way of preventing this tampering would be to create a closure. In this context, a closure is a technique for creating a function scope for the explicit purpose of limiting access to a set of values. Consider this example:

This is useful, but it has one major drawback: you can’t share variables between scopes. Given a class or a constructor function, values that are created within a closure could not be shared by other methods of the class. This forces us to create new instances of every function that may access these values. This can lead to clumsy class definitions and complicate long-term maintenance. Not to mention, it’s ugly.

function Database() < const authenticaton = 'Actually private'; // Less versatile this.connect = function() < // can access the authentication token >; > // More memory efficient Database.prototype.connect = function() < // cannot access the authentication token >

An ideal implementation of a private variable would let me define functions on the prototype, while also restricting access to the functions defined on the class.

Читайте также:  Python сборка из исходников

Protected Scope

A typical implementation of a protected scope blends some of the features of public and private scope and is the hardest scope to reproduce in JavaScript. The two important features of a protected scope, in my estimation, are (1) a protected value must be shared across all layers in the prototype chain; and (2) a protected value must not be accessible from outside of the object.

Putting a protected value in the public scope is a poor solution because it would not place any limits on accessing that value:

However, putting a protected scope is also a poor solution because it would not allow sub-classes or parent-classes to access the value:

class Database < constructor() < const authentication = "I'm a private property!"; this.connect = function() < alert('I can access ' + authentication); >; > > class CoolDatabase extends Database < constructor() < this.connect = function() < alert("I can't access [authentication] at all! :("); >; > connect() < alert("I also can't access [authentication] #foiledagain"); >>

An ideal solution to this problem would allow any method within an object’s prototype chain to access a value, while also denying access by any other object. In JavaScript, that’s a pretty tall order.

The Solution

We don’t need to do anything to get public scope — this is the default behavior! To get private and protected scopes, however, we need a way to grant access to values across functional scopes based on the context. For this behavior, a WeakMap is a near-perfect solution!

At the time of this writing, WeakMaps are supported across all major browsers on mobile and desktop (yay!). If you aren’t familiar with WeakMaps — and you can be forgiven for not knowing — they are key-value stores that do not prevent garbage collection on their keys. This makes them preferable to a Map object because Maps still track values, and it makes them preferable to Dictionaries because they can use non-primitive keys.

A Simple Private Implementation

One way of implementing a simple private scope would be to declare a WeakMap and a class within the same closure. This would let us instantiate new objects, grant cross-function access to the private scope, and prevent access to the WeakMap from outside of the closure:

const Database = (function() < const $private = new WeakMap(); function constructor() < $private.set(this, < authentication: "I'm a private variable" >); > constructor.prototype.connect = function() < // I can access the private values $private.get(this).authentication; >return constructor; >)(); // No way to access $private out here const db = new Database();

Unfortunately, this solution won’t let us share the WeakMap with subclasses. In order to do that, the WeakMap must be declared outside of the closure. My solution to the problem is to put the WeakMap in a separate module and import it as necessary.

My Implementation

My solution to the problem of private and protected scope is to create a separate module. Both scopes are implemented with WeakMaps and both scopes index on the this context. In the name of simplicity (and also laziness) I prefer to strip away the getters, setters, and other functions for the map and obscure them behind a class:

// Wrap our container with a simplified interface function getAccessor(container) < // Simplify the container's interface: return function(context) < if (!container.has(context)) < container.set(context, <>); > return container.get(context); > > function createScope() < return < $private: getAccessor(new WeakMap()), >; >

If createScope is executed within the class-definition closure, then it works essentially like the simple private implementation.

const Database = (function() < const < $private >= createScope(); function constructor() < $private(this).authentication = "Still super-private"; >constructor.prototype.connect = function() < alert('I can still access ' + $private(this).authentication); >; return constructor; >)();

But what about protected variables?

Читайте также:  File path format python

I’m glad you asked! In order to create a protected variable, we can create a WeakMap within the scoping module:

const protectedMap = new WeakMap(); // Wrap our container with a simplified interface function getAccessor(container) < // Simplify the container's interface: return function(context) < if (!container.has(context)) < container.set(context, <>); > return container.get(context); > > function createScope() < return < $private: getAccessor(new WeakMap()), $protected: getAccessor(protectedMap), >; >

Although this isn’t a perfect solution, it does allow us to share values between classes. Consider these two classes:

// Base Class const Base = (function() < const < $protected, $private >= createScope(); return class Base < constructor() < $private(this).value = "I'm a private variable"; $protected(this).value = "I'm a protected variable"; >getBasePrivate() < return $private(this).value; >getBaseProtected() < return $protected(this).value; >>; >)(); // Sub Class const Sub = (function() < const < $protected, $private >= createScope(); return class Sub extends Base < constructor() < super(); $private(this).value = "I'm also a private variable"; $protected(this).value = "I'm also a protected variable"; >getSubPrivate() < return $private(this).value; >getSubProtected() < return $protected(this).value; >>; >)();

In this example, the Parent and Sub classes have separate private scopes, and overlapping protected scopes:

const base = new Base(); const sub = new Sub(); base.getBasePrivate(); // "I'm a private variable" base.getBaseProtected(); // "I'm a protected variable" sub.getBasePrivate(); // "I'm a private variable" sub.getSubPrivate(); // "I'm also a private variable" sub.getBaseProtected(); // "I'm also a protected variable" sub.getSubProtected(); // "I'm also a protected variable"

Conclusion

This isn’t a perfect solution, but it is my favorite. WeakMaps are now broadly supported in all major browsers, and most of my JavaScript work is done in Node.js anyway. Not to mention, the syntax makes for some really obvious code. As a cherry-on-top benefit, this method also lets instances of the same class access each others’ private members — which should be familiar territory for many software engineers.

There are two minor drawbacks to using this method:

  1. A WeakMap is significantly slower than attaching values directly to an object. In my testing, I get between 1-million and 2-million accesses per second.
  2. The protected accessor can be used anywhere, so it’s mostly security by obscurity. Remember that JavaScript exposes the source-code to anyone who cares to look, so don’t expect this to be a silver-bullet for security.

For my purposes, this is good enough until the private and protected keywords make their way into the official language specifications. If these drawbacks are too severe for any problem that I’m approaching, then JavaScript probably isn’t the right language for the job in the first place.

If you want to incorporate this into your own projects, feel free to install the StD Scope module from NPM. If you just want to take a look at a completed module, head over to my GitHub to take a look at the repository.

Источник

JavaScript: Публичные и приватные поля классов

Несколько предложений расширяют существующий синтаксис классов в JavaScript новой функциональностью. Эта статья объясняет новый синтаксис публичных полей классов в V8 v7.2 и Chrome 72, а также грядущих приватных полей.

Вот пример кода, который создает экземпляр класса IncreasingCounter:

const counter = new IncreasingCounter(); counter.value; // logs 'Getting the current value!' // → 0 counter.increment(); counter.value; // logs 'Getting the current value!' // → 1

Отметим, что обращение к value выполняет некоторый код (вывод сообщения в лог) перед тем, как вернуть значение. Теперь спросите себя: как бы Вы реализовали этот класс на JavaScript?

Читайте также:  Вывести 100 рандомных чисел питон

Классы ES2015

Ниже пример того, как класс IncreasingCounter может быть реализован с помощью синтаксиса ES2015:

class IncreasingCounter < constructor() < this._count = 0; >get value() < console.log('Getting the current value!'); return this._count; >increment() < this._count++; >>

Класс предоставляет геттер value и метод для инкремента значения в прототипе. Более любопытно, что класс имеет конструктор, который инициирует свойство _count и выставляет его начальное значение в 0. Сейчас мы используем префикс подчеркивания, чтобы обозначить, что _count не должен использоваться напрямую вне класса, но это просто соглашение; в действительности это не приватное свойство, а эта семантика не определена в самом языке.

const counter = new IncreasingCounter(); counter.value; // logs 'Getting the current value!' // → 0 // Nothing stops people from reading or messing with the // `_count` instance property. counter._count; // → 0 counter._count = 42; counter.value; // logs 'Getting the current value!' // → 42

Публичные поля классов

Новый синтаксис для публичных полей позволяет упростить определение класса:

class IncreasingCounter < _count = 0; get value() < console.log('Getting the current value!'); return this._count; >increment() < this._count++; >>

Свойство _count теперь лаконично объявлено в начале класса. Нам больше не нужен конструктор только для того, чтобы определить некоторые поля. Отлично!

Тем не менее, _count — все еще публичное свойство. А в этом конкретном примере мы хотим предотвратить обращение к этому полю напрямую.

Приватные поля классов

Именно здесь на помощь приходят приватные поля. Новый синтаксис для приватных полей схож с синтаксисом публичных полей, за исключением того, что Вы помечаете их как приватные, используя символ #. Вы можете думать, что # — это просто часть имени поля:

class IncreasingCounter < #count = 0; get value() < console.log('Getting the current value!'); return this.#count; >increment() < this.#count++; >>

Приватные поля недоступны вне тела класса:

const counter = new IncreasingCounter(); counter.#count; // → SyntaxError counter.#count = 42; // → SyntaxError

Статические свойства

Синтаксис полей классов может быть использован для создания публичных и приватных статических свойств и методов, как показано ниже:

class FakeMath < // `PI` is a static public property. static PI = 22 / 7; // Close enough. // `#totallyRandomNumber` is a static private property. static #totallyRandomNumber = 4; // `#computeRandomNumber` is a static private method. static #computeRandomNumber() < return FakeMath.#totallyRandomNumber; >// `random` is a static public method (ES2015 syntax) // that consumes `#computeRandomNumber`. static random() < console.log('I heard you like random numbers…') return FakeMath.#computeRandomNumber(); >> FakeMath.PI; // → 3.142857142857143 FakeMath.random(); // logs 'I heard you like random numbers…' // → 4 FakeMath.#totallyRandomNumber; // → SyntaxError FakeMath.#computeRandomNumber(); // → SyntaxError

Упрощение работы с подклассами

Преимущества нового синтаксиса полей классов становятся более очевидны при работе с подклассами, которые вводят дополнительные поля. Представим следующий базовый класс Animal:

Чтобы создать подкласс Cat, который добавляет новое свойство для экземпляра, ранее требовалось обратиться к super(), чтобы вызвать конструктор базового класса Animal перед тем, как создать это свойство:

class Cat extends Animal < constructor(name) < super(name); this.likesBaths = false; >meow() < console.log('Meow!'); >>

Здесь много шаблонного кода только для того, чтобы указать, что коты не очень любят принимать ванну. К счастью, новый синтаксис полей классов избавляет от необходимости определения этого конструктора с неуклюжим вызовом super():

Итого

Публичные поля классов доступны, начиная с V8 v7.2 и Chrome 72. Скоро планируется релиз и приватных полей классов.

Источник

Оцените статью