Apa motivasi untuk membawa Simbol ke ES6?

368

UPDATE : Baru-baru ini artikel brilian dari Mozilla muncul. Bacalah jika Anda penasaran.

Seperti yang Anda ketahui, mereka berencana untuk memasukkan tipe primitif Simbol baru dalam ECMAScript 6 (belum lagi beberapa hal gila lainnya). Saya selalu berpikir bahwa :symbolgagasan di Ruby tidak perlu; kita bisa dengan mudah menggunakan string biasa, seperti yang kita lakukan pada JavaScript. Dan sekarang mereka memutuskan untuk mempersulit hal-hal di JS dengan itu.

Saya tidak mengerti motivasinya. Bisakah seseorang menjelaskan kepada saya apakah kita benar-benar membutuhkan simbol dalam JavaScript?

Yanis
sumber
6
Saya tidak tahu seberapa otentik penjelasan ini, tapi ini awal: tc39wiki.calculist.org/es6/symbols .
Felix Kling
8
Simbol memungkinkan begitu banyak , mereka memungkinkan pengidentifikasi unik lingkup pada objek. Misalnya, memiliki properti pada objek yang hanya dapat diakses di satu tempat.
Benjamin Gruenbaum
5
Tidak yakin tentang itu karena Anda dapat menggunakan Object.getOwnPropertySymbols (o)
Yanis
4
Ini lebih keunikan daripada privasi.
Qantas 94 Heavy
2
Mereka akan memiliki implementasi kelas yang lebih rumit dengan kata kunci atribut kelas privatedan publicmereka memutuskan untuk parit untuk implementasi kelas yang lebih sederhana. Alih-alih this.x = xAnda seharusnya melakukan public x = xdan untuk variabel pribadi private y = y. Mereka memutuskan untuk membuang itu untuk implementasi kelas yang jauh lebih minimal. Simbol kemudian akan menjadi solusi yang diperlukan untuk mendapatkan properti pribadi dalam implementasi minimal.
lyschoening

Jawaban:

224

Motivasi asli untuk memperkenalkan simbol ke Javascript adalah untuk mengaktifkan properti pribadi .

Sayangnya, mereka akhirnya diturunkan peringkatnya. Mereka tidak lagi pribadi, karena Anda dapat menemukannya melalui refleksi, misalnya, menggunakan Object.getOwnPropertySymbolsatau proksi.

Mereka sekarang dikenal sebagai simbol unik dan satu-satunya tujuan penggunaannya adalah untuk menghindari bentrokan nama antar properti. Sebagai contoh, ECMAScript sendiri sekarang dapat memperkenalkan kait ekstensi melalui metode tertentu yang dapat Anda letakkan pada objek (misalnya untuk mendefinisikan protokol iterasi mereka) tanpa risiko mereka berbenturan dengan nama pengguna.

Apakah itu cukup kuat, motivasi untuk menambahkan simbol ke bahasa masih bisa diperdebatkan.

Andreas Rossberg
sumber
93
Sebagian besar bahasa (semua yang umum afaik) menyediakan beberapa mekanisme, biasanya refleksi, untuk mendapatkan akses ke pribadi.
Esailija
19
@ Eailija, saya tidak berpikir itu benar - khususnya, karena banyak bahasa tidak menawarkan refleksi di tempat pertama. Membocorkan keadaan pribadi melalui refleksi (seperti misalnya di Jawa) harus dianggap sebagai bug, bukan fitur. Ini terutama berlaku pada halaman web, di mana memiliki negara pribadi yang andal dapat relevan dengan keamanan. Saat ini, satu-satunya cara untuk mencapainya di JS adalah melalui penutupan, yang bisa membosankan dan mahal.
Andreas Rossberg
38
Mekanismenya tidak harus berupa refleksi - C ++, Java, C #, Ruby, Python, PHP, Objective-C semuanya memungkinkan akses dengan satu atau lain cara jika memang diinginkan. Ini bukan tentang kemampuan tetapi komunikasi.
Esailija
4
@plalx, ​​di web, enkapsulasi terkadang juga tentang keamanan.
Andreas Rossberg
3
@RolandPihlakas, sayangnya, Object.getOwnPropertySymbolsbukan satu-satunya kebocoran; yang lebih sulit adalah kemampuan untuk menggunakan proxy untuk mencegat akses ke properti "pribadi".
Andreas Rossberg
95

Simbol tidak menjamin privasi sejati tetapi dapat digunakan untuk memisahkan properti publik dan internal objek. Mari kita ambil contoh di mana kita dapat menggunakan Symboluntuk memiliki properti pribadi.

Mari kita ambil contoh di mana properti objek tidak pribadi.

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

Di atas, Petproperti kelas typetidak pribadi. Untuk menjadikannya pribadi, kita harus membuat penutupan. Contoh di bawah ini mengilustrasikan bagaimana kita dapat membuat typepribadi menggunakan penutupan.

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

Kerugian dari pendekatan di atas: Kami memperkenalkan penutupan ekstra untuk setiap Petinstance yang dibuat, yang dapat merusak kinerja.

Sekarang kami perkenalkan Symbol. Ini dapat membantu kami menjadikan properti pribadi tanpa menggunakan penutupan tambahan yang tidak perlu. Contoh kode di bawah ini:

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
sumber
15
Perhatikan bahwa properti simbol tidak pribadi ! Simbol bebas tabrakan . Anda mungkin ingin membaca jawaban yang diterima.
Bergi
3
Ya, simbol tidak menjamin privasi sebenarnya tetapi dapat digunakan untuk memisahkan properti publik dan internal objek. Maaf, lupa menambahkan poin ini ke jawaban saya. Akan memperbarui jawaban saya sesuai.
Samar Panda
@Amaramaranda, Anda mungkin juga mengatakan bahwa anggota awalan dengan _tidak menjamin privasi sebenarnya tetapi dapat digunakan untuk memisahkan properti publik dan internal objek. Dengan kata lain, jawaban sia-sia.
Pacerier
10
Saya tidak akan mengatakan sia-sia, karena simbol secara default tidak dapat dihitung, juga tidak dapat diakses oleh 'kesalahan', sementara kunci lainnya bisa.
Patrick
5
Saya menemukan jawaban Anda satu-satunya yang benar-benar memiliki contoh yang masuk akal, mengapa Anda ingin mendefinisikan atribut pribadi objek sebagai Simbol, bukan hanya atribut normal.
Luis Lobo Borobia
42

Symbolsadalah jenis objek baru dan khusus yang dapat digunakan sebagai nama properti unik dalam objek. Menggunakan Symbolbukannya stringmemungkinkan modul yang berbeda untuk membuat properti yang tidak saling bertentangan. Symbolsjuga dapat dibuat pribadi, sehingga propertinya tidak dapat diakses oleh siapa pun yang belum memiliki akses langsung ke Symbol.

Symbolsadalah primitif baru . Sama seperti number, string, dan booleanprimitif, Symbolmemiliki fungsi yang dapat digunakan untuk membuat mereka. Berbeda dengan primitif lainnya, Symbolstidak memiliki sintaks literal (misalnya bagaimana stringmemiliki '') - satu-satunya cara untuk membuat mereka adalah dengan Symbolkonstruktor dengan cara berikut:

let symbol = Symbol();

Pada kenyataannya, Symbolitu hanya cara yang sedikit berbeda untuk melampirkan properti ke objek - Anda dapat dengan mudah memberikan yang dikenal Symbolssebagai metode standar, seperti Object.prototype.hasOwnPropertyyang muncul di semua yang mewarisi dari Object.

Berikut adalah beberapa manfaat dari Symboltipe primitif.

Symbols memiliki debuggability bawaan

Symbols dapat diberikan deskripsi, yang sebenarnya hanya digunakan untuk debugging untuk membuat hidup sedikit lebih mudah ketika masuk ke konsol.

Symbolsdapat digunakan sebagai Objectkunci

Di sinilah Symbolmenjadi sangat menarik. Mereka sangat terkait dengan objek. Symboldapat ditetapkan sebagai kunci ke objek, artinya Anda dapat menetapkan jumlah unik yang tidak terbatas Symbolpada objek dan dijamin bahwa ini tidak akan pernah bertentangan dengan stringkunci, atau unik lainnya Symbols.

Symbols dapat digunakan sebagai nilai unik.

Mari kita asumsikan Anda memiliki perpustakaan logging, yang meliputi beberapa tingkat log seperti logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARNdan sebagainya. Dalam kode ES5 Anda ingin membuatnya string(jadi logger.levels.DEBUG === 'debug'), atau numbers ( logger.levels.DEBUG === 10). Keduanya tidak ideal karena nilai-nilai itu bukan nilai unik, tetapi Symbolmemang! Jadi logger.levelsmenjadi:

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

Baca lebih lanjut di artikel hebat ini .

Mihai Alexandru-Ionut
sumber
10
Saya tidak yakin saya mengerti teladan Anda, dan mengapa Anda perlu log.levels = {DEBUG: Symbol('debug')dan tidak sederhana log.levels = {DEBUG:'debug'}. pada akhirnya sama saja. Saya pikir perlu disebutkan bahwa Simbol tidak terlihat ketika beralih pada kunci Object. itu "benda" mereka
vsync
Salah satu manfaatnya adalah seseorang tidak dapat secara tidak sengaja menggunakan literal dan percaya itu akan berhasil selamanya. (Perhatikan bahwa ini bukan argumen yang sangat kuat, karena seseorang dapat menggunakan {}dan mencapai hasil yang sama (sebagai nilai unik), atau mungkin literal lebih disukai dalam proyek itu, atau Anda dapat mengatakan seseorang harus membaca dokumen terlebih dahulu.) I secara pribadi berpikir itu memberikan keterbacaan yang baik dari makna yang unik dalam kode
apple apple
perhatikan ketika digunakan sebagai nilai unik, objek literal juga memiliki built-in debuggability yaitu Symbol("some message")menjadi {message:'some message'}, bisa dibilang objek melakukan lebih baik di sini karena Anda dapat menambahkan beberapa bidang.
apel apel
38

Posting ini adalah tentang Symbol(), disertakan dengan contoh aktual yang dapat saya temukan / buat dan fakta & definisi yang dapat saya temukan.

TLDR;

Ini Symbol()adalah tipe data, diperkenalkan dengan rilis ECMAScript 6 (ES6).

Ada dua fakta aneh tentang Simbol.

  • tipe data pertama dan hanya tipe data dalam JavaScript yang tidak memiliki literal

  • variabel apa pun, yang didefinisikan dengan Symbol(), mendapat konten unik, tetapi tidak benar - benar pribadi .

  • setiap data memiliki simbolnya sendiri , dan untuk data yang sama simbolnya akan sama . Info selengkapnya di paragraf berikut, kalau tidak itu bukan TLRD; :)

Bagaimana cara menginisialisasi simbol?

1. Untuk mendapatkan pengidentifikasi unik dengan nilai yang dapat diperdebatkan

Anda dapat melakukannya dengan cara ini:

var mySymbol1 = Symbol();

Atau dengan cara ini:

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

The "some text here"String tidak dapat diekstraksi dari simbol, itu hanya keterangan untuk keperluan debugging. Itu tidak mengubah perilaku simbol dengan cara apa pun. Meskipun, Anda bisa console.logmelakukannya (yang adil, karena nilainya untuk debugging, agar tidak salah bahwa log dengan beberapa entri log lainnya):

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

2. Untuk mendapatkan simbol untuk beberapa data string

Dalam hal ini nilai simbol sebenarnya diperhitungkan dan dengan cara ini dua simbol mungkin tidak unik.

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

Sebut saja simbol-simbol itu "simbol tipe kedua". Mereka tidak berpotongan dengan simbol "tipe pertama" (yaitu yang didefinisikan dengan Symbol(data)) dengan cara apa pun.

Dua paragraf berikutnya hanya menyinggung simbol tipe pertama .

Bagaimana saya mendapat manfaat dari menggunakan Simbol daripada tipe data yang lebih lama?

Pertama mari kita pertimbangkan objek, tipe data standar. Kita dapat mendefinisikan beberapa pasangan nilai kunci di sana dan memiliki akses ke nilai-nilai dengan menentukan kunci.

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

Bagaimana jika kita memiliki dua orang dengan nama Peter?

Melakukan ini:

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

tidak masuk akal.

Jadi, tampaknya menjadi masalah dua orang yang sama sekali berbeda memiliki nama yang sama. Mari kita simak yang baru Symbol(). Ini seperti seseorang dalam kehidupan nyata - siapa pun itu unik , tetapi nama mereka bisa sama. Mari kita mendefinisikan dua "orang".

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

Sekarang kami memiliki dua orang yang berbeda dengan nama yang sama. Apakah orang kita memang berbeda? Mereka; Anda dapat memeriksa ini:

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

Apa manfaatnya bagi kami?

Kami dapat membuat dua entri di objek Anda untuk orang yang berbeda dan mereka tidak dapat salah dengan cara apa pun.

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

Catatan:
Perlu diperhatikan, bahwa mengencangkan objek dengan JSON.stringifyakan menjatuhkan semua pasangan yang diinisialisasi dengan Simbol sebagai kunci.
Eksekusi Object.keystidak akan mengembalikan Symbol()->valuepasangan tersebut.

Menggunakan inisialisasi ini, sangat mustahil untuk keliru entri untuk orang pertama dan kedua. Memanggil console.logmereka akan dengan benar menampilkan nama kedua mereka.

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

Ketika digunakan dalam objek, bagaimana perbedaannya dibandingkan dengan mendefinisikan properti yang tidak dapat dihitung?

Memang, sudah ada cara untuk mendefinisikan properti untuk disembunyikan dari Object.keysdan penghitungan. Ini dia:

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

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

Apa perbedaan yang terjadi di Symbol()sana? Perbedaannya adalah Anda masih bisa mendapatkan properti yang didefinisikan dengan Object.definePropertycara biasa:

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

Dan jika didefinisikan dengan Simbol seperti pada paragraf sebelumnya:

fruit = Symbol("apple");

Anda akan memiliki kemampuan untuk menerima nilainya hanya jika mengetahui variabelnya, yaitu

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

Selain itu, mendefinisikan properti lain di bawah kunci "apple"akan membuat objek menjatuhkan yang lebih lama (dan jika hard-coded, itu bisa menimbulkan kesalahan). Jadi, tidak ada lagi apel! Sangat disayangkan. Mengacu pada paragraf sebelumnya, Simbol itu unik dan mendefinisikan kunci yang Symbol()akan membuatnya unik.

Ketik konversi dan pemeriksaan

  • Tidak seperti tipe data lainnya, tidak mungkin untuk mengkonversi Symbol()ke tipe data lain.

  • Dimungkinkan untuk "membuat" simbol berdasarkan tipe data primitif dengan menelepon Symbol(data).

  • Dalam hal memeriksa tipe, tidak ada yang berubah.

    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
sumber
Apakah ini dimigrasikan dari Dokumentasi SO?
Knu
1
@ KNU bukan itu; Saya telah mengumpulkan info dan menulis jawaban ini sendiri
nicael
Jawaban yang sangat indah!
Mihai Alexandru-Ionut
1
Jawaban yang bagus pada Symbol, namun saya masih tidak tahu mengapa saya menggunakan objek dengan tombol simbol daripada array. Jika saya memiliki banyak orang seperti {"peter": "pan"} {"john": "doe"} rasanya buruk bagi saya untuk meletakkannya di satu objek. Untuk alasan yang sama seperti saya tidak membuat kelas dengan properti duplikat seperti personFirstName1, personFirstName2. Ini dikombinasikan dengan ketidakmampuan untuk menyesuaikannya, saya tidak melihat manfaat hanya kerugian.
Eldo
18

Beginilah cara saya melihatnya. Simbol memberikan 'tingkat privasi tambahan', dengan mencegah kunci / properti objek tidak diekspos melalui beberapa metode populer seperti Object.keys () dan 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…)

Meskipun diberi objek per se, properti seperti itu masih dapat diekspos melalui refleksi, proxy, Object.getOwnPropertySymbols () dll., Tidak ada cara alami untuk mengaksesnya melalui beberapa metode langsung, yang kadang-kadang cukup dari perspektif OOP.

Chong Lip Phang
sumber
2

Simbol JS adalah tipe data primitif baru. Mereka adalah token yang berfungsi sebagai ID unik . Simbol dapat dibuat menggunakan Symbolkonstruktor. Ambil contoh cuplikan ini dari 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.

Seringkali berguna untuk menggunakan simbol sebagai kunci properti objek unik, misalnya:

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
sumber
0

Simbol memiliki dua kasus penggunaan utama:

  1. "Objek tersembunyi" properti. Jika kita ingin menambahkan properti ke objek yang “milik” skrip atau pustaka lain, kita bisa membuat simbol dan menggunakannya sebagai kunci properti. Properti simbolik tidak muncul di for..in, sehingga tidak akan diproses secara tidak sengaja bersama-sama dengan properti lainnya. Juga tidak akan diakses secara langsung, karena skrip lain tidak memiliki simbol kita. Jadi properti akan dilindungi dari penggunaan yang tidak disengaja atau ditimpa.

    Jadi kita dapat "secara diam-diam" menyembunyikan sesuatu ke objek yang kita butuhkan, tetapi orang lain tidak boleh melihat, menggunakan properti simbolik.

  2. Ada banyak simbol sistem yang digunakan oleh JavaScript yang dapat diakses sebagai Symbol.*. Kita dapat menggunakannya untuk mengubah beberapa perilaku bawaan. Misalnya, ...... Symbol.iteratoruntuk iterables, Symbol.toPrimitiveuntuk mengatur konversi objek-ke-primitif dan sebagainya.

Sumber

snr
sumber