Jaka jest motywacja wprowadzenia symboli do ES6?

368

AKTUALIZACJA : Niedawno pojawił się genialny artykuł z Mozilli . Przeczytaj, jeśli jesteś ciekawy.

Jak zapewne wiesz, planują dołączyć nowy typ prymitywny Symbol do ECMAScript 6 (nie wspominając o innych zwariowanych rzeczach). Zawsze myślałem, że :symbolpojęcie w Ruby jest niepotrzebne; zamiast tego moglibyśmy z łatwością używać zwykłych ciągów, tak jak w JavaScript. A teraz postanawiają to skomplikować w JS.

Nie rozumiem motywacji. Czy ktoś mógłby mi wyjaśnić, czy naprawdę potrzebujemy symboli w JavaScript?

Yanis
źródło
6
Nie wiem, jak autentyczne jest to wyjaśnienie, ale jest to początek: tc39wiki.calculist.org/es6/symbols .
Felix Kling
8
Symbole umożliwiają tak wiele , że pozwalają na unikalne identyfikatory w zakresie obiektów. Na przykład posiadanie właściwości obiektów, które są dostępne tylko w jednym miejscu.
Benjamin Gruenbaum
5
Nie jestem tego pewien, ponieważ możesz użyć Object.getOwnPropertySymbols (o)
Yanis
4
To bardziej wyjątkowość niż prywatność.
Qantas 94 Heavy
2
Mieli mieć bardziej skomplikowaną implementację klasy privatei publicsłowa kluczowe atrybutu klasy, którą postanowili porzucić w celu uproszczenia implementacji klasy. Zamiast tego this.x = xmiałeś zrobić public x = xi dla zmiennych prywatnych private y = y. Zdecydowali się to porzucić, aby uzyskać znacznie bardziej minimalną implementację klasy. Symbol byłby wówczas wymaganym obejściem, aby uzyskać prywatne właściwości przy minimalnej implementacji.
lizosening

Odpowiedzi:

224

Oryginalną motywacją do wprowadzenia symboli do Javascript było włączenie własności prywatnych .

Niestety ostatecznie zostali poważnie obniżeni. Nie są już prywatne, ponieważ można je znaleźć przez odbicie, na przykład za pomocą Object.getOwnPropertySymbolslub proxy.

Są one teraz znane jako unikalne symbole, a ich jedynym przeznaczeniem jest unikanie konfliktów nazw między właściwościami. Na przykład sam ECMAScript może teraz wprowadzać rozszerzenia za pomocą pewnych metod, które można umieszczać na obiektach (np. W celu zdefiniowania ich protokołu iteracji) bez ryzyka kolizji z nazwami użytkowników.

Czy jest to wystarczająco silna motywacja do dodania symboli do języka jest dyskusyjna.

Andreas Rossberg
źródło
93
Większość języków (wszystkie z głównego nurtu) zapewniają pewien mechanizm, zwykle refleksyjny, aby uzyskać dostęp do prywatnego.
Esailija
19
@Esailija, nie sądzę, że to prawda - w szczególności, ponieważ wiele języków nie oferuje refleksji. Wyciekanie stanu prywatnego przez odbicie (jak np. W Javie) powinno być traktowane jako błąd, a nie funkcja. Jest to szczególnie prawdziwe na stronach internetowych, gdzie posiadanie wiarygodnego stanu prywatnego może być istotne dla bezpieczeństwa. Obecnie jedynym sposobem na osiągnięcie tego w JS są zamknięcia, które mogą być zarówno żmudne, jak i kosztowne.
Andreas Rossberg
38
Mechanizm nie musi być odbiciem - C ++, Java, C #, Ruby, Python, PHP, Objective-C pozwalają na dostęp w taki czy inny sposób, jeśli ktoś naprawdę chce. Tak naprawdę nie chodzi o umiejętności, ale o komunikację.
Esailija
4
@plalx, ​​w Internecie, enkapsulacja czasem dotyczy również bezpieczeństwa.
Andreas Rossberg
3
@RolandPihlakas niestety Object.getOwnPropertySymbolsnie jest jedynym wyciekiem; tym trudniejsza jest możliwość korzystania z serwerów proxy w celu przechwycenia dostępu do „prywatnej” własności.
Andreas Rossberg
95

Symbole nie gwarantują prawdziwej prywatności, ale można ich użyć do oddzielenia publicznych i wewnętrznych właściwości obiektów. Weźmy przykład, w którym możemy użyć Symboldo posiadania prywatnych nieruchomości.

Weźmy przykład, w którym właściwość obiektu nie jest prywatna.

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

Powyżej Petwłaściwość klasy typenie jest prywatna. Aby uczynić go prywatnym, musimy stworzyć zamknięcie. Poniższy przykład ilustruje, w jaki sposób możemy uczynić typeprywatnym za pomocą zamknięcia.

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

Wada powyższego podejścia: Wprowadzamy dodatkowe zamknięcie dla każdej Petutworzonej instancji, co może zaszkodzić wydajności.

Teraz przedstawiamy Symbol . Może to pomóc nam uczynić nieruchomość prywatną bez użycia dodatkowych niepotrzebnych zamknięć. Przykład kodu poniżej:

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
Samar Panda
źródło
15
Zauważ, że właściwości symboli nie są prywatne ! Symbole nie powodują kolizji . Możesz przeczytać zaakceptowaną odpowiedź.
Bergi
3
Tak, symbol nie gwarantuje prawdziwej prywatności, ale można go wykorzystać do oddzielenia publicznych i wewnętrznych właściwości obiektów. Przepraszam, zapomniałem dodać ten punkt do mojej odpowiedzi. Zaktualizuje odpowiednio moją odpowiedź.
Samar Panda
@SamarPanda, równie dobrze możesz powiedzieć, że prefiksowanie członków _nie gwarantuje prawdziwej prywatności, ale może być użyte do oddzielenia publicznych i wewnętrznych właściwości obiektów. Innymi słowy, bezcelowa odpowiedź.
Pacerier
10
Nie powiedziałbym, że nie ma sensu, ponieważ symbole domyślnie nie są policzalne, nie można też uzyskać dostępu przez „pomyłkę”, podczas gdy każdy inny klucz może.
Patrick
5
Uważam, że twoja odpowiedź jest jedyną, która ma przykład, który ma sens, dlaczego chcesz zdefiniować prywatny atrybut obiektu jako Symbol, a nie tylko zwykły atrybut.
Luis Lobo Borobia,
42

Symbolssą nowym, specjalnym rodzajem obiektu, który może być używany jako unikalna nazwa właściwości w obiektach. Użycie Symbolzamiast string„pozwala” na tworzenie różnych modułów, które nie powodują konfliktów między sobą. Symbolsmożna również ustawić jako prywatny, aby ich właściwości nie były dostępne dla osób, które nie mają jeszcze bezpośredniego dostępu do Symbol.

Symbolssą nowym prymitywem . Podobnie jak number, stringi booleanprymitywów, Symbolposiada funkcję, która może być użyta do ich tworzenia. W przeciwieństwie do innych prymitywów, Symbolsnie używaj literalnej składni (np. Jak to stringzrobić '') - jedynym sposobem ich utworzenia jest użycie Symbolkonstruktora w następujący sposób:

let symbol = Symbol();

W rzeczywistości Symbolsą tylko nieco innym sposobem dołączania właściwości do obiektu - możesz łatwo podać znane Symbolsmetody standardowe, tak jak to Object.prototype.hasOwnPropertywystępuje we wszystkim, co dziedziczy poObject .

Oto niektóre z zalet Symbolpierwotnego typu.

Symbols mają wbudowaną możliwość debuggowania

Symbols można podać opis, który jest tak naprawdę używany do debugowania, aby ułatwić sobie życie podczas logowania do konsoli.

Symbolsmogą być używane jako Objectklucze

To jest Symbolnaprawdę interesujące. Są mocno splecione z przedmiotami. Symbolmożna przypisać jako klucze do obiektów, co oznacza, że ​​możesz przypisać nieograniczoną liczbę unikalnych Symbolobiektów do obiektu i mieć pewność, że nigdy nie będą one kolidować z stringkluczami lub innymi unikatowymi Symbols.

Symbols może być użyty jako unikalna wartość.

Załóżmy, że masz biblioteki rejestrowania, która obejmuje wiele poziomów dziennika, takich jak logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARNi tak dalej. W kodzie ES5 chcesz ustawić te string(tak logger.levels.DEBUG === 'debug') lub numbers ( logger.levels.DEBUG === 10). Oba nie są idealne, ponieważ te wartości nie są wartościami unikalnymi, ale Symbolsą! Po logger.levelsprostu staje się:

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

Przeczytaj więcej w tym świetnym artykule .

Mihai Alexandru-Ionut
źródło
10
Nie jestem pewien, czy rozumiem twój przykład i dlaczego miałbyś go potrzebować, log.levels = {DEBUG: Symbol('debug')a nie po prostu log.levels = {DEBUG:'debug'}. na koniec jest tak samo. Myślę, że warto wspomnieć, że symbole są niewidoczne podczas iteracji po kluczach obiektu. to jest ich „rzecz”
vsync,
Jedną z korzyści jest to, że ktoś nie może przypadkowo użyć literału i wierzyć, że zadziała to wiecznie. (Zauważ, że nie jest to naprawdę mocny argument, ponieważ można po prostu użyć {}i osiągnąć ten sam wynik (jako unikalną wartość), a może literał jest preferowany w tym projekcie, lub możesz powiedzieć, że musisz najpierw przeczytać dokument.) osobiście uważam, że zapewnia dobrą czytelność unikalnego znaczenia w kodzie
jabłko jabłko
uwaga, gdy jest używana jako unikalna wartość, dosłowność obiektu ma również wbudowaną możliwość debuggowania, tzn. Symbol("some message")staje się {message:'some message'}, prawdopodobnie obiekt ma się tu lepiej, ponieważ można dodać wiele pól.
jabłko jabłko
38

Ten post dotyczy Symbol() dostarczonych rzeczywistych przykładów, które mogłem znaleźć / stworzyć, oraz faktów i definicji, które mogłem znaleźć.

TLDR;

Jest Symbol()to typ danych wprowadzony wraz z wydaniem ECMAScript 6 (ES6).

Istnieją dwa ciekawe fakty na temat Symbolu.

  • pierwszy typ danych i jedyny typ danych w JavaScript, który nie ma literału

  • każda zmienna zdefiniowana za pomocą Symbol()otrzymuje unikalną treść, ale nie jest tak naprawdę prywatna .

  • wszelkie dane mają swój własny Symbol, a dla tych samych danych Symbole byłyby takie same . Więcej informacji w następnym akapicie, w przeciwnym razie nie jest to TLRD; :)

Jak zainicjować symbol?

1. Aby uzyskać unikalny identyfikator z wartością do debugowania

Możesz to zrobić w ten sposób:

var mySymbol1 = Symbol();

Lub w ten sposób:

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

"some text here"Ciąg nie mogą być pozyskiwane z symbolem, to tylko opis dla celów debugowania. Nie zmienia to w żaden sposób zachowania symbolu. Chociaż możesz to console.logzrobić (co jest sprawiedliwe, ponieważ wartość służy do debugowania, aby nie pomylić tego dziennika z innym wpisem):

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

2. Aby uzyskać symbol dla niektórych danych łańcuchowych

W tym przypadku wartość symbolu jest faktycznie brana pod uwagę i w ten sposób dwa symbole mogą nie być unikalne.

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

Nazwijmy te symbole symbolami „drugiego rodzaju”. Nie przecinają się z symbolami „pierwszego typu” (tj. Tymi, które są zdefiniowane za pomocąSymbol(data) w żaden sposób z ).

Następne dwa akapity dotyczą tylko pierwszego typu symbolu.

Jak korzystać z Symbol zamiast starszych typów danych?

Rozważmy najpierw obiekt, standardowy typ danych. Możemy zdefiniować tam kilka par klucz-wartość i uzyskać dostęp do tych wartości, określając klucz.

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

Co jeśli mamy dwie osoby o imieniu Piotr?

Robiąc to:

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

nie miałoby sensu.

Wydaje się więc, że jest to problem dwóch absolutnie różnych osób o tym samym nazwisku. Odwołajmy się zatem do nowych Symbol(). To jak osoba w prawdziwym życiu - każda osoba jest wyjątkowa , ale jej nazwiska mogą być równe. Zdefiniujmy dwie „osoby”.

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

Teraz mamy dwie różne osoby o tym samym nazwisku. Czy nasze osoby są naprawdę różne? Oni są; możesz to sprawdzić:

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

Jak na tym skorzystamy?

Możemy dokonać dwóch wpisów w Twoim obiekcie dla różnych osób i nie można ich w żaden sposób pomylić.

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

Uwaga:
Warto jednak zauważyć, że strunowanie obiektu JSON.stringifyspowoduje usunięcie wszystkich par zainicjowanych Symbolem jako kluczem.
Wykonanie Object.keysnie zwróci takich Symbol()->valuepar.

Dzięki tej inicjalizacji absolutnie niemożliwe jest pomylenie wpisów dla pierwszej i drugiej osoby. Wołanie console.logo nie poprawnie wypisze ich drugie imię.

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

W przypadku użycia w obiekcie, czym różni się od definiowania właściwości niepoliczalnej?

Rzeczywiście istniał już sposób zdefiniowania właściwości, przed którą należy ukryć Object.keysi wyliczenia. Oto on:

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

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

Jaką różnicę Symbol()tam przynosi? Różnica polega na tym, że nadal można uzyskać właściwość zdefiniowaną Object.definePropertyw zwykły sposób:

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

A jeśli zdefiniowano za pomocą Symbolu jak w poprzednim akapicie:

fruit = Symbol("apple");

Będziesz mógł otrzymać jego wartość tylko wtedy, gdy znasz jej zmienną, tj

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

Co więcej, zdefiniowanie innej właściwości pod kluczem "apple"spowoduje, że obiekt porzuci starszą (a jeśli zostanie zakodowany na stałe, może wyrzucić błąd). Nie ma już jabłek! Szkoda. Odnosząc się do poprzedniego akapitu, Symbole są unikalne i definiują klucz, Symbol()który uczyni go unikalnym.

Konwersja i sprawdzanie typów

  • W przeciwieństwie do innych typów danych, nie można przekonwertować na Symbol()inny typ danych.

  • Możliwe jest „uczynienie” symbolu opartym na prymitywnym typie danych przez wywołanie Symbol(data).

  • Pod względem sprawdzania typu nic się nie zmienia.

    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

Nicość
źródło
Czy migracja została przeprowadzona z dokumentacji SO?
Knu
1
@KNU nie było; Zebrałem informacje i sam napisałem tę odpowiedź
Nicol
Naprawdę piękna odpowiedź!
Mihai Alexandru-Ionut
1
Świetna odpowiedź na Symbol, jednak wciąż nie wiem, dlaczego miałbym używać obiektu z kluczami symboli zamiast tablicy. Jeśli mam wiele osób, takich jak {„peter”: „pan”} {„john”: „doe”}, źle jest dla mnie umieścić je w jednym obiekcie. Z tego samego powodu, dla którego nie tworzę klas ze zduplikowanymi właściwościami, takimi jak personFirstName1, personFirstName2. W połączeniu z brakiem możliwości jego zsztywnienia nie widzę korzyści, tylko wady.
eldo
18

Oto jak to widzę. Symbole zapewniają „dodatkowy poziom prywatności”, zapobiegając ujawnieniu kluczy / właściwości obiektu za pomocą popularnych metod, takich jak Object.keys () i 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…)

Mimo że dany obiekt jako taki, takie właściwości mogą być nadal ujawniane przez odbicie, proxy, Object.getOwnPropertySymbols () itp., Nie ma naturalnych sposobów dostępu do nich za pomocą kilku bezpośrednich metod, które mogą być czasami wystarczające z perspektywy OOP.

Chong Lip Phang
źródło
2

Symbol JS to nowy prymitywny typ danych. Są to tokeny, które służą jako unikalne identyfikatory . Symbol można utworzyć za pomocą Symbolkonstruktora. Weźmy na przykład ten fragment kodu z 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.

Często przydatne jest używanie symboli jako unikatowych kluczy właściwości obiektu, na przykład:

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

Willem van der Veen
źródło
0

Symbole mają dwa główne przypadki użycia:

  1. Właściwości „ukrytego” obiektu. Jeśli chcemy dodać właściwość do obiektu, który „należy” do innego skryptu lub biblioteki, możemy utworzyć symbol i użyć go jako klucza właściwości. Właściwość symboliczna nie pojawia się wfor..in , więc nie będzie przypadkowo przetwarzana razem z innymi właściwościami. Również nie będzie dostępny bezpośrednio, ponieważ inny skrypt nie ma naszego symbolu. Tak więc właściwość będzie chroniona przed przypadkowym użyciem lub nadpisaniem.

    Możemy więc „ukryć” ukryć coś w potrzebnych nam obiektach, ale inni nie powinni tego widzieć, używając właściwości symbolicznych.

  2. Istnieje wiele symboli systemowych używanych przez JavaScript, które są dostępne jako Symbol.*. Możemy ich użyć do zmiany niektórych wbudowanych zachowań. Na przykład ...... Symbol.iteratordla iteracji, Symbol.toPrimitiveaby skonfigurować konwersję obiekt-prymityw i tak dalej.

Źródło

snr
źródło