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


368

ОНОВЛЕННЯ : Нещодавно вийшла блискуча стаття Mozilla . Прочитайте, якщо вам цікаво.

Як ви можете знати, вони планують включити новий примітивний тип Symbol в ECMAScript 6 (не кажучи вже про деякі інші шалені речі). Я завжди думав, що :symbolпоняття в Рубі марне; ми могли б замість цього використовувати звичайні рядки, як у JavaScript. І тепер вони вирішують ускладнити речі в JS з цим.

Я не розумію мотивації. Може хтось пояснить мені, чи справді нам потрібні символи в JavaScript?


6
Я не знаю, наскільки справжнє це пояснення, але це початок: tc39wiki.calculist.org/es6/symbols .
Фелікс Клінг

8
Символи дозволяють настільки багато , вони дозволяють визначати масштаби унікальних ідентифікаторів на об'єктах. Наприклад, наявність властивостей на об'єктах, доступних лише в одному місці.
Бенджамін Груенбаум

5
Не впевнений у цьому, оскільки ви можете використовувати Object.getOwnPropertySymbols (o)
Yanis

4
Це більше унікальність, а не конфіденційність.
Qantas 94 Важкий

2
Вони матимуть більш складну реалізацію класів із ключовими словами privateта publicатрибутами класу, які вирішили позбутися для більш простої реалізації класу. Замість this.x = xвас повинні були зробити public x = xі для приватних змінних private y = y. Вони вирішили відмовитися від цього для набагато більш мінімальної реалізації класу. Потім символ буде необхідним рішенням для отримання приватних властивостей при мінімальній реалізації.
ліскощінг

Відповіді:


224

Початковою мотивацією для введення символів у Javascript було включення приватних властивостей.

На жаль, вони закінчилися сильно зниженими. Вони більше не є приватними, оскільки їх можна знайти за допомогою рефлексії, наприклад, за допомогою Object.getOwnPropertySymbolsабо проксі.

Зараз вони відомі як унікальні символи, і їх єдиним призначенням є уникнення зіткнень між властивостями. Наприклад, сам ECMAScript може вводити гачки розширення за допомогою певних методів, які можна нанести на об'єкти (наприклад, визначити їх протокол ітерації), не ризикуючи зіткнутися з іменами користувачів.

Чи є це достатньо сильним мотивацією для додавання символів до мови, є дискусійним.


93
Більшість мов (усі основні мови afaik) надають певний механізм, як правило, відображення, щоб отримати доступ до приватного в будь-якому випадку.
Есаїлія

19
@Esailija, я не думаю, що це правда - зокрема, оскільки багато мов не пропонують роздумів в першу чергу. Витік приватного стану через відображення (наприклад, на Java) слід вважати помилкою, а не особливістю. Особливо це стосується веб-сторінок, де наявність надійного приватного стану може бути важливим для безпеки. В даний час єдиний спосіб досягти цього в JS - це закриття, яке може бути як стомлюючим, так і дорогим.
Андреас Россберг

38
Цей механізм не повинен бути відображеним - C ++, Java, C #, Ruby, Python, PHP, Objective-C дозволяють отримати доступ так чи інакше, якщо дійсно хочеться. Йдеться не про вміння, а про спілкування.
Есаїлія

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замість strings дозволяє різним модулям створювати властивості, які не суперечать один одному. 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 (so 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


Це було перенесено з документації на SO?
Кну

1
@KNU це не було; Я зібрав інформацію і написав цю відповідь сам
нікель

Дійсно красива відповідь!
Михай Александру-Іонут

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 () тощо, але немає природних засобів для доступу до них за допомогою декількох прямих методів, що може бути достатньо інколи з точки зору OOP.


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для встановлення об'єкта в примітивне перетворення тощо.

Джерело

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.