Какова мотивация для переноса символов на ES6?

368

ОБНОВЛЕНИЕ : Недавно вышла блестящая статья от Mozilla . Прочитайте это, если вам интересно.

Как вы, возможно, знаете, они планируют включить новый тип примитива Symbol в ECMAScript 6 (не говоря уже о некоторых других сумасшедших вещах). Я всегда думал, что :symbolпонятие в Ruby не нужно; вместо этого мы могли бы легко использовать простые строки, как в JavaScript. И теперь они решили все усложнить в JS этим.

Я не понимаю мотивацию. Может ли кто-нибудь объяснить мне, действительно ли нам нужны символы в JavaScript?

Яниса
источник
6
Я не знаю, насколько достоверным является это объяснение, но это начало: tc39wiki.calculist.org/es6/symbols .
Феликс Клинг
8
Символы позволяют так много , что позволяют определять уникальные идентификаторы на объектах. Например, наличие свойств объектов, которые доступны только в одном месте.
Бенджамин Грюнбаум
5
Не уверен насчет этого, поскольку вы можете использовать Object.getOwnPropertySymbols (o)
Yanis
4
Это больше уникальность, чем конфиденциальность.
Qantas 94 Heavy
2
Они собирались иметь более сложную реализацию класса с privateи publicключевыми словами атрибутов класса , что они решили канаву для более простой реализации класса. Вместо this.x = xвас должны были делать public x = xи частные переменные private y = y. Они решили отказаться от этого для гораздо более минимальной реализации класса. Тогда Symbol будет необходимым обходным путем для получения частных свойств в минимальной реализации.
лишение свободы

Ответы:

224

Первоначальной мотивацией для введения символов в Javascript было включение частных свойств.

К сожалению, они оказались серьезно понижены. Они больше не являются частными, так как вы можете найти их с помощью отражения, например, с помощью Object.getOwnPropertySymbolsили прокси.

Теперь они известны как уникальные символы, и их единственное предназначение - избежать столкновения имен между свойствами. Например, сам ECMAScript теперь может вводить хуки расширения с помощью определенных методов, которые вы можете наложить на объекты (например, для определения их протокола итерации), не рискуя столкнуть их с именами пользователей.

Независимо от того, что является достаточно сильным побуждением для добавления символов языка является дискуссионным.

Андреас Россберг
источник
93
Большинство языков (все основные - afaik) предоставляют какой-то механизм, обычно рефлексию, чтобы в любом случае получить доступ к приватному.
Esailija
19
@Esailija, я не думаю, что это правда - в частности, так как многие языки не предлагают отражения в первую очередь. Утечка приватного состояния через отражение (например, в Java) должна рассматриваться как ошибка, а не как функция. Это особенно верно для веб-страниц, где наличие надежного частного состояния может иметь отношение к безопасности. В настоящее время единственный способ добиться этого в JS - это замыкания, которые могут быть утомительными и дорогостоящими.
Андреас Россберг
38
Механизм не должен быть отражением - C ++, Java, C #, Ruby, Python, PHP, Objective-C - все так или иначе разрешают доступ, если кто-то действительно этого хочет. Дело не в способностях, а в общении.
Esailija
4
@plalx, ​​в Интернете инкапсуляция иногда тоже касается безопасности.
Андреас Россберг
3
@RolandPihlakas, к сожалению, Object.getOwnPropertySymbolsне единственная утечка; более сложным является возможность использования прокси для перехвата доступа к «частному» свойству.
Андреас Россберг
95

Символы не гарантируют истинную конфиденциальность, но могут использоваться для разделения общих и внутренних свойств объектов. Давайте рассмотрим пример, в котором мы можем использовать Symbolчастные свойства.

Давайте рассмотрим пример, когда свойство объекта не является частным.

var Pet = (function() {
  function Pet(type) {
    this.type = type;
  }
  Pet.prototype.getType = function() {
    return this.type;
  }
  return Pet;
}());

var a = new Pet('dog');
console.log(a.getType());//Output: dog
a.type = null;
//Modified outside
console.log(a.getType());//Output: null

Выше Petсвойство класса typeне является частным. Чтобы сделать это частным, мы должны создать закрытие. Приведенный ниже пример иллюстрирует, как мы можем сделать typeприватным использование замыкания.

var Pet = (function() {
  function Pet(type) {
    this.getType = function(){
      return type;
    };
  }
  return Pet;
}());

var b = new Pet('dog');
console.log(b.getType());//dog
b.type = null;
//Stays private
console.log(b.getType());//dog

Недостаток вышеуказанного подхода: мы вводим дополнительное закрытие для каждого Petсозданного экземпляра, что может снизить производительность.

Теперь мы представляем Symbol. Это может помочь нам сделать собственность частной, не используя лишних ненужных замыканий. Пример кода ниже:

var Pet = (function() {
  var typeSymbol = Symbol('type');
  function Pet(type) {
    this[typeSymbol] = type;
  }
  Pet.prototype.getType = function(){
    return this[typeSymbol];
  }
  return Pet;
}());

var a = new Pet('dog');
console.log(a.getType());//Output: dog
a.type = null;
//Stays private
console.log(a.getType());//Output: dog
Самар Панда
источник
15
Обратите внимание, что свойства символа не являются частными ! Символы без столкновений . Вы можете прочитать принятый ответ.
Берги
3
Да, символ не гарантирует истинную конфиденциальность, но может использоваться для разделения общих и внутренних свойств объектов. Извините, забыл добавить этот пункт в мой ответ. Буду обновлять мой ответ соответственно.
Самар Панда
@ SamarPanda, вы также можете сказать, что добавление префиксов к членам _не гарантирует истинную конфиденциальность, но может использоваться для разделения открытых и внутренних свойств объектов. Другими словами, бессмысленный ответ.
Pacerier
10
Я бы не сказал, что бессмысленно, так как символы по умолчанию не перечисляются, к ним также нельзя получить доступ по ошибке, как любой другой ключ.
Патрик
5
Я считаю, что ваш ответ единственный, в котором есть пример, который имеет смысл: почему вы хотите определить закрытый атрибут объекта как символ, а не просто обычный атрибут.
Луис Лобо Боробия
42

Symbolsэто новый, особый вид объекта, который можно использовать как уникальное имя свойства в объектах. Использование Symbolвместо stringпозволяет различным модулям создавать свойства, которые не конфликтуют друг с другом. Symbolsтакже может быть закрытым, чтобы к его свойствам не мог получить доступ никто, кто еще не имеет прямого доступа к Symbol.

Symbolsновый примитив . Точно так же как number, stringи booleanпримитивы, Symbolесть функция , которая может быть использована для их создания. В отличие от других примитивов, Symbolsне имеют буквального синтаксиса (например, как stringесть '') - единственный способ создать их с помощью Symbolконструктора следующим образом:

let symbol = Symbol();

На самом деле, Symbolэто просто немного другой способ прикрепления свойств к объекту - вы можете легко предоставить общеизвестные Symbolsкак стандартные методы, точно такие, Object.prototype.hasOwnPropertyкоторые появляются во всем, что наследуется от Object.

Вот некоторые из преимуществ Symbolпримитивного типа.

Symbols иметь встроенную функцию отладки

Symbols можно дать описание, которое на самом деле просто используется для отладки, чтобы немного облегчить жизнь при регистрации их на консоли.

Symbolsможно использовать как Objectключи

Это где Symbolдействительно интересно. Они сильно переплетены с предметами. Symbolможно назначать в качестве ключей для объектов, то есть вы можете назначить неограниченное количество уникальных Symbolобъектов и гарантировать, что они никогда не будут конфликтовать с stringключами или другими уникальными Symbols.

Symbols может использоваться как уникальное значение.

Давайте предположим , что у вас есть библиотека протоколирования, которая включает в себя несколько уровней журналов , таких как logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARNи так далее. В коде ES5 вы хотели бы сделать эти strings (так logger.levels.DEBUG === 'debug') или numbers ( logger.levels.DEBUG === 10). Оба из них не идеальны, так как эти значения не являются уникальными значениями, но Symbolесть! Так logger.levelsпросто становится:

log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');

Узнайте больше в этой замечательной статье .

Михай Александру-Ионут
источник
10
Я не уверен, что понимаю ваш пример, и зачем вам это нужно, log.levels = {DEBUG: Symbol('debug')а не просто log.levels = {DEBUG:'debug'}. в конце это то же самое. Я думаю, что стоит упомянуть, что символы невидимы при переборе ключей объекта. это их "вещь"
vsync
Одним из преимуществ является то, что кто-то не может случайно использовать литерал и полагать, что он будет работать вечно. (Обратите внимание, что это не очень сильный аргумент, так как можно просто использовать {}и достичь того же результата (в качестве уникального значения), или, возможно, в этом проекте предпочтителен литерал, или вы можете сказать, что сначала нужно прочитать документ.) Я лично думаю, что это обеспечивает хорошую читабельность уникального значения в коде
apple apple
обратите внимание, что при использовании в качестве уникального значения, литерал объекта также имеет встроенную функцию отладки, то есть Symbol("some message")становится {message:'some message'}, возможно, объект здесь лучше, так как вы можете добавить несколько полей.
яблоко яблоко
38

Этот пост о Symbol(), снабженный фактическими примерами, которые я мог найти / сделать, и фактами и определениями, которые я мог найти.

TLDR;

Тип Symbol()данных, представленный в выпуске ECMAScript 6 (ES6).

Есть два любопытных факта о Символе.

  • первый тип данных и единственный тип данных в JavaScript, который не имеет литерала

  • любая переменная, определенная с помощью Symbol(), получает уникальный контент, но на самом деле он не является частным .

  • любые данные имеют свой собственный символ, и для тех же данных символы будут одинаковыми . Больше информации в следующем параграфе, иначе это не TLRD; :)

Как мне инициализировать символ?

1. Получить уникальный идентификатор с отладочным значением

Вы можете сделать это так:

var mySymbol1 = Symbol();

Или так:

var mySymbol2 = Symbol("some text here");

"some text here"Строка не может быть извлечена из символа, это просто описание для целей отладки. Это никак не меняет поведение символа. Хотя, вы могли бы console.logэто сделать (что справедливо, поскольку значение используется для отладки, чтобы не перепутать этот журнал с какой-либо другой записью журнала):

console.log(mySymbol2);
// Symbol(some text here)

2. Получить символ для некоторых строковых данных

В этом случае значение символа фактически учитывается, и таким образом два символа могут быть неуникальными.

var a1 = Symbol.for("test");
var a2 = Symbol.for("test");
console.log(a1 == a2); //true!

Давайте назовем эти символы символами второго типа. Они никак не пересекаются с символами «первого типа» (т. Е. Теми, которые определены с помощью Symbol(data)).

Следующие два абзаца относятся только к символу первого типа .

Как извлечь выгоду из использования Symbol вместо старых типов данных?

Давайте сначала рассмотрим объект, стандартный тип данных. Мы могли бы определить несколько пар ключ-значение и получить доступ к значениям, указав ключ.

var persons = {"peter":"pan","jon":"doe"};
console.log(persons.peter);
// pan

Что если у нас есть два человека с именем Питер?

Делая это:

var persons = {"peter":"first", "peter":"pan"};

не имеет особого смысла.

Таким образом, возникает проблема двух абсолютно разных людей, имеющих одинаковые имена. Давайте тогда ссылаться на новое Symbol(). Это как человек в реальной жизни - любой человек уникален , но его имена могут быть одинаковыми. Давайте определим два «человека».

 var a = Symbol("peter");
 var b = Symbol("peter");

Теперь у нас есть два разных человека с одинаковыми именами. Наши люди действительно разные? Они есть; Вы можете проверить это:

 console.log(a == b);
 // false

Какую пользу мы получаем там?

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

 var firstPerson = Symbol("peter");
 var secondPerson = Symbol("peter");
 var persons = {[firstPerson]:"first", [secondPerson]:"pan"};

Примечание.
Следует отметить, что при приведении объекта в JSON.stringifyпорядок все пары, инициализированные символом в качестве ключа, будут отброшены.
Выполнение Object.keysтакже не вернет такие Symbol()->valueпары.

Используя эту инициализацию, абсолютно невозможно перепутать записи для первого и второго лица. Вызов console.logдля них правильно выведет их вторые имена.

 console.log(persons[a]);
 // first
 console.log(persons[b]);
 // pan

Когда он используется в объекте, чем он отличается от определения не перечислимого свойства?

Действительно, уже существовал способ определения свойства, от которого необходимо скрыть, Object.keysи перечисления. Вот:

var anObject = {};
var fruit = "apple";    

Object.defineProperty( anObject, fruit, {
    enumerable: false,
    value: "green"
});

Какая разница Symbol()? Разница в том, что вы все еще можете получить свойство, определенное Object.definePropertyобычным способом:

console.log(anObject[fruit]); //green
console.log(anObject["apple"]); //green
console.log(anObject.apple); //green

И если определено с символом, как в предыдущем абзаце:

fruit = Symbol("apple");

У вас будет возможность получить его значение, только если вы знаете его переменную, т.е.

console.log(anObject[fruit]); //green
console.log(anObject["apple"]); //undefined
console.log(anObject.apple); //undefined

Более того, определение другого свойства под ключом "apple"приведет к тому, что объект отбросит более старое (и если его жестко закодировать, это может привести к ошибке). Так что, яблок больше нет! Какая жалость. Ссылаясь на предыдущий абзац, символы являются уникальными и определяют ключ, Symbol()который сделает его уникальным.

Преобразование типов и проверка

  • В отличие от других типов данных, невозможно преобразовать их Symbol()в любой другой тип данных.

  • Можно «сделать» символ, основанный на примитивном типе данных, путем вызова Symbol(data).

  • С точки зрения проверки типа ничего не меняется.

    function isSymbol ( variable ) {
        return typeof someSymbol === "symbol";
    }
    
    var a_Symbol = Symbol("hey!");
    var totally_Not_A_Symbol = "hey";
    
    console.log(isSymbol(a_Symbol)); //true
    console.log(isSymbol(totally_Not_A_Symbol)); //false

nicael
источник
Было ли это перенесено из SO документации?
Кну
1
@KNU это не было; Я собрал информацию и написал этот ответ сам
Nicael
Действительно красивый ответ!
Михай Александру-Ионут
1
Отличный ответ на Symbol, однако я до сих пор не знаю, зачем мне использовать объект с символьными ключами вместо массива. Если у меня есть несколько человек, таких как {"peter": "pan"} {"john": "doe"}, мне неприятно помещать их в один объект. По той же причине, по которой я не создаю классы с дублированными свойствами, такими как personFirstName1, personFirstName2. Это в сочетании с неспособностью привести его в соответствие, я не вижу преимуществ, а только недостатков.
ELDO
18

Вот как я это вижу. Символы обеспечивают «дополнительный уровень конфиденциальности», предотвращая раскрытие ключей / свойств объекта с помощью некоторых популярных методов, таких как Object.keys () и JSON.stringify ().

var age = Symbol();  // declared in another module perhaps?
class Person {
   constructor(n,a){
      this.name = n;
      this[age] = a;  
   }
   introduce(){
       console.log(`My name is ${this.name}. I am ${this[age]-10}.`);
   }
}
var j = new Person('Jane',45);
j.introduce();  // My name is Jane. I am 35.
console.log(JSON.stringify(j)); // {"name":"Jane"}
console.log(Object.keys(j)); // ["name"]
console.log(j[age]); // 45   (well…only if you know the age in the first place…)

Несмотря на то, что объект задан как таковой, такие свойства все еще могут быть доступны через рефлексию, прокси, Object.getOwnPropertySymbols () и т. Д., Но нет естественных средств для доступа к ним через несколько прямых методов, которых иногда может быть достаточно с точки зрения ООП.

Чонг Лип Панг
источник
2

Символ JS - это новый примитивный тип данных. Это токены, которые служат уникальными идентификаторами . Символ может быть создан с помощью Symbolконструктора. Взять, к примеру, этот фрагмент из MDN:

// The symbol constructor takes one optional argument, 
// the descriptions which is used for debugging only.
// Here are two symbols with the same description
let Sym1 = Symbol("Sym");
let Sym2 = Symbol("Sym");
  
console.log(Sym1 == Sym2); // returns "false"
// Symbols are guaranteed to be unique.
// Even if we create many symbols with the same description,
// they are different values.

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

let obj = {};
let prop = Symbol();

obj[prop] = 123;  // the symbol prop is assigned 123
obj.prop  = 456;  // the string prop is assigned 456

console.log(obj.prop, obj[prop]); // logs 456, 123

Виллем ван дер Веен
источник
0

Символы имеют два основных варианта использования:

  1. «Скрытые» свойства объекта. Если мы хотим добавить свойство в объект, который «принадлежит» другому сценарию или библиотеке, мы можем создать символ и использовать его в качестве ключа свойства. Символьное свойство не отображается внутри for..in, поэтому оно не будет случайно обработано вместе с другими свойствами. Также он не будет доступен напрямую, потому что другой скрипт не имеет нашего символа. Таким образом, имущество будет защищено от случайного использования или перезаписи.

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

  2. Есть много системных символов, используемых JavaScript, которые доступны как Symbol.*. Мы можем использовать их, чтобы изменить некоторые встроенные поведения. Например, ...... Symbol.iteratorдля итераций, Symbol.toPrimitiveдля настройки преобразования объекта в примитив и так далее.

Источник

ОСШ
источник