5

Приватные свойства в классах JavaScript ES6

13

Заголовок: Возможно ли создать приватные свойства в классах ES6?

Описание проблемы:

Как можно запретить доступ к свойству instance.property в классах ES6? Вот пример:

class Something {
  constructor() {
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); // => "test"

Я хотел бы выяснить, существует ли способ сделать свойство property приватным, чтобы его нельзя было получить из экземпляра класса.

5 ответ(ов)

3

Обновление: см. ответы других пользователей, это устарело.

Краткий ответ: нет, в ES6 нет встроенной поддержки приватных свойств.

Тем не менее, вы можете имитировать это поведение, не прикрепляя новые свойства к объекту, а храня их внутри конструктора класса. Затем вы можете использовать геттеры и сеттеры для доступа к скрытым свойствам. Обратите внимание, что геттеры и сеттеры пересоздаются для каждого нового экземпляра класса.

ES6

class Person {
    constructor(name) {
        var _name = name;
        this.setName = function(name) { _name = name; };
        this.getName = function() { return _name; };
    }
}

ES5

function Person(name) {
    var _name = name;
    this.setName = function(name) { _name = name; };
    this.getName = function() { return _name; };
}
0

Ответ: "Нет". Однако вы можете создать приватный доступ к свойствам следующим образом:

  • Используйте модули. Все в модуле является приватным, если не сделано публичным с помощью ключевого слова export.
  • Внутри модулей используйте замыкания: http://www.kirupa.com/html5/closures_in_javascript.htm

(Упоминание о том, что Символы могут быть использованы для обеспечения приватности, было верным в более ранних версиях спецификации ES6, но в настоящее время это не так: https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html и https://stackoverflow.com/a/22280202/1282216. Для более детального обсуждения Символов и приватности смотрите: https://curiosity-driven.org/private-properties-in-javascript)

0

Единственный способ обеспечить настоящую приватность в JavaScript — это использование области видимости, поэтому невозможно создать свойство, которое будет членом this и доступно только внутри компонента. Лучший способ хранения действительно приватных данных в ES6 — это использование WeakMap.

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "Я Приватный1");
    privateProp2.set(this, "Я Приватный2");

    this.publicVar = "Я публичный";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

Очевидно, что этот подход может быть медленным и определенно не эстетичным, но он действительно обеспечивает приватность.

Имейте в виду, что даже это решение не является идеальным, поскольку JavaScript очень динамичен. Кто-то все равно может сделать следующее:

var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Сохранить 'this', 'key' и 'value'
    return oldSet.call(this, key, value);
};

Поэтому если вы хотите быть особенно осторожными, вам нужно будет сохранить локальную ссылку на методы .set и .get для явного использования, вместо того чтобы полагаться на переопределяемый прототип.

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "Я Приватный1");
    WMSet.call(privateProp2, this, "Я Приватный2");

    this.publicVar = "Я публичный";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}

Таким образом, использование WeakMap является хорошим подходом для обеспечения приватности в JavaScript, несмотря на некоторые его ограничения.

0

Использование ES6 модулей (изначально предложенное @d13) хорошо работает для меня. Хотя это не идеальная замена приватным свойствам, вы можете быть уверены, что свойства, которые должны быть приватными, не будут доступны извне вашего класса. Вот пример:

something.js

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

А код, который использует этот класс, может выглядеть так:

import Something from './something.js';

const something = new Something('Солнечный день!');
something.say(); // Солнечный день!
something._message; // undefined
something._greet(); // исключение

Обновление (Важно):

Как отметил @DanyalAytekin в комментариях, эти приватные свойства статичны, следовательно, они глобальны по своей области видимости. Они хорошо работают при создании одиночек (Singleton), но необходимо быть осторожным с временными объектами. Вот расширенный пример:

import Something from './something.js';
import Something2 from './something.js';

const a = new Something('а');
a.say(); // а

const b = new Something('б');
b.say(); // б

const c = new Something2('в');
c.say(); // в

a.say(); // в
b.say(); // в
c.say(); // в

Таким образом, несмотря на использование ES6 модулей, вам нужно быть внимательным к контексту и области видимости ваших "приватных" свойств.

0

Дополнив предложения @d13 и комментарии @johnny-oshika и @DanyalAytekin, я думаю, что в приведенном примере от @johnny-oshika можно использовать обычные функции вместо стрелочных, а затем привязать их с помощью .bind к текущему объекту и объекту _privates в виде карифицированного параметра:

something.js

function _greet(_privates) {
  return 'Hello ' + _privates.message;
}

function _updateMessage(_privates, newMessage) {
  _privates.message = newMessage;
}

export default class Something {
  constructor(message) {
    const _privates = {
      message
    };

    this.say = _greet.bind(this, _privates);
    this.updateMessage = _updateMessage.bind(this, _privates);
  }
}

main.js

import Something from './something.js';

const something = new Something('Sunny day!');

const message1 = something.say();
something.updateMessage('Cloudy day!');
const message2 = something.say();

console.log(message1 === 'Hello Sunny day!');  // true
console.log(message2 === 'Hello Cloudy day!');  // true

// следующее не является публичным
console.log(something._greet === undefined);  // true
console.log(something._privates === undefined);  // true
console.log(something._updateMessage === undefined);  // true

// еще один экземпляр, который не делит _privates
const something2 = new Something('another Sunny day!');

const message3 = something2.say();

console.log(message3 === 'Hello another Sunny day!'); // true

Преимущества, которые я могу выделить:

  • Мы можем иметь приватные методы (_greet и _updateMessage ведут себя как приватные методы, пока мы не export их ссылки).
  • Несмотря на то, что они не находятся в прототипе, упомянутые методы сэкономят память, так как экземпляры создаются один раз, вне класса (в отличие от определения их в конструкторе).
  • Мы не утечки какие-либо глобальные переменные, так как мы находимся внутри модуля.
  • Мы также можем иметь приватные свойства, используя привязанный объект _privates.

Некоторые недостатки, которые я могу выделить:

  • Менее интуитивно.
  • Смешанное использование синтаксиса классов и старых паттернов (привязка объектов, переменные с областью видимости модуля/функции).
  • Жесткие привязки - мы не можем перепривязывать публичные методы (хотя мы можем улучшить это, используя мягкие привязки).

Запускать пример кода можно здесь: http://www.webpackbin.com/NJgI5J8lZ.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь