Động lực để mang Biểu tượng đến ES6 là gì?


368

CẬP NHẬT : Gần đây, một bài báo xuất sắc từ Mozilla đã xuất hiện. Đọc nó nếu bạn tò mò.

Như bạn có thể biết họ đang có kế hoạch đưa loại nguyên thủy Biểu tượng mới vào ECMAScript 6 (không đề cập đến một số nội dung điên rồ khác). Tôi luôn nghĩ rằng :symbolkhái niệm trong Ruby là không cần thiết; thay vào đó, chúng ta có thể dễ dàng sử dụng các chuỗi đơn giản, giống như chúng ta làm trong JavaScript. Và bây giờ họ quyết định làm phức tạp mọi thứ trong JS với điều đó.

Tôi không hiểu động cơ. Ai đó có thể giải thích cho tôi liệu chúng ta có thực sự cần các biểu tượng trong JavaScript không?


6
Tôi không biết giải thích này xác thực như thế nào, nhưng đó là một sự khởi đầu: tc39wiki.calculist.org/es6/symbols .
Felix Kling

8
Các biểu tượng cho phép rất nhiều , chúng cho phép các định danh duy nhất có phạm vi trên các đối tượng. Ví dụ: có các thuộc tính trên các đối tượng chỉ có thể truy cập ở một nơi.
Benjamin Gruenbaum

5
Không chắc chắn về điều đó vì bạn có thể sử dụng Object.getOwnPropertySymbols (o)
Yanis

4
Đó là sự độc đáo hơn là sự riêng tư.
Qantas 94 Nặng

2
Họ sẽ có một triển khai lớp phức tạp hơn với các từ khóa thuộc tính lớp privatepublichọ đã quyết định bỏ qua để thực hiện lớp đơn giản hơn. Thay vì this.x = xbạn phải làm public x = xvà cho các biến riêng tư private y = y. Họ quyết định bỏ qua việc thực hiện lớp tối thiểu hơn nhiều. Biểu tượng sau đó sẽ là một cách giải quyết bắt buộc để có được các thuộc tính riêng tư trong việc thực hiện tối thiểu.
lyschoen

Câu trả lời:


224

Động lực ban đầu để giới thiệu các biểu tượng cho Javascript là cho phép các thuộc tính riêng tư .

Thật không may, cuối cùng họ đã bị hạ cấp nghiêm trọng. Chúng không còn riêng tư nữa, vì bạn có thể tìm thấy chúng thông qua sự phản chiếu, ví dụ, bằng cách sử dụng Object.getOwnPropertySymbolshoặc proxy.

Bây giờ chúng được gọi là biểu tượng duy nhất và mục đích sử dụng duy nhất của chúng là để tránh xung đột tên giữa các thuộc tính. Ví dụ, bản thân ECMAScript hiện có thể giới thiệu các hook mở rộng thông qua các phương thức nhất định mà bạn có thể đặt trên các đối tượng (ví dụ: để xác định giao thức lặp của chúng) mà không mạo hiểm để chúng đụng độ với tên người dùng.

Cho dù đó là đủ mạnh một động lực để thêm các biểu tượng cho ngôn ngữ là tranh cãi.


93
Hầu hết các ngôn ngữ (tất cả các ngôn ngữ chính afaik) cung cấp một số cơ chế, thường là sự phản ánh, để có quyền truy cập vào chế độ riêng tư.
Esailija

19
@Esailija, tôi không nghĩ đó là sự thật - đặc biệt, vì nhiều ngôn ngữ không cung cấp sự phản ánh ở nơi đầu tiên. Rò rỉ trạng thái riêng tư thông qua sự phản chiếu (như ví dụ trong Java) nên được coi là một lỗi, không phải là một tính năng. Điều này đặc biệt đúng trên các trang web, nơi có trạng thái riêng tư đáng tin cậy có thể liên quan đến bảo mật. Hiện tại, cách duy nhất để đạt được nó trong JS là thông qua các lần đóng, có thể vừa tẻ nhạt vừa tốn kém.
Andreas Rossberg

38
Cơ chế không phải là sự phản chiếu - C ++, Java, C #, Ruby, Python, PHP, Objective-C đều cho phép truy cập theo cách này hay cách khác nếu thực sự muốn. Đó không thực sự là về khả năng mà là giao tiếp.
Esailija

4
@plalx, ​​trên web, đóng gói đôi khi cũng là về bảo mật.
Andreas Rossberg

3
@RolandPihlakas, thật không may, Object.getOwnPropertySymbolskhông phải là rò rỉ duy nhất; điều khó khăn hơn là khả năng sử dụng proxy để chặn quyền truy cập vào một tài sản "riêng tư".
Andreas Rossberg

95

Các biểu tượng không đảm bảo quyền riêng tư thực sự nhưng có thể được sử dụng để phân tách các thuộc tính công cộng và nội bộ của các đối tượng. Hãy lấy một ví dụ về nơi chúng ta có thể sử dụng Symbolđể có các thuộc tính riêng tư.

Hãy lấy một ví dụ trong đó một thuộc tính của một đối tượng không riêng tư.

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

Ở trên, Pettài sản lớp typekhông phải là tư nhân. Để làm cho nó riêng tư chúng ta phải tạo ra một đóng cửa. Ví dụ dưới đây minh họa cách chúng ta có thể đặt typeriêng tư bằng cách đóng.

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

Nhược điểm của cách tiếp cận trên: Chúng tôi đang giới thiệu một đóng thêm cho mỗi Pettrường hợp được tạo, có thể gây hại cho hiệu suất.

Bây giờ chúng tôi giới thiệu Symbol. Điều này có thể giúp chúng tôi làm cho một tài sản riêng tư mà không cần sử dụng thêm các đóng cửa không cần thiết. Mã ví dụ dưới đây:

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
Lưu ý rằng các thuộc tính biểu tượng không riêng tư ! Các biểu tượng không va chạm . Bạn có thể muốn đọc câu trả lời được chấp nhận.
Bergi

3
Có, biểu tượng không đảm bảo quyền riêng tư thực sự nhưng có thể được sử dụng để phân tách các thuộc tính công khai và nội bộ của các đối tượng. Xin lỗi, quên thêm điểm này vào câu trả lời của tôi. Sẽ cập nhật câu trả lời của tôi cho phù hợp.
Samar Panda

@SamarPanda, Bạn cũng có thể nói rằng các thành viên tiền tố _không đảm bảo quyền riêng tư thực sự nhưng có thể được sử dụng để phân tách các thuộc tính công khai và nội bộ của các đối tượng. Nói cách khác, câu trả lời vô nghĩa.
Pacerier

10
Tôi sẽ không nói vô nghĩa, vì các biểu tượng theo mặc định là không thể đếm được, cũng không thể truy cập bằng 'lỗi', trong khi bất kỳ khóa nào khác có thể.
Patrick

5
Tôi thấy câu trả lời của bạn là câu trả lời duy nhất thực sự có ý nghĩa, về lý do tại sao bạn muốn xác định thuộc tính riêng của đối tượng là Biểu tượng, thay vì chỉ là một thuộc tính thông thường.
Luis Lobo Borobia

42

Symbolslà một loại đối tượng mới, đặc biệt có thể được sử dụng làm tên thuộc tính duy nhất trong các đối tượng. Sử dụng Symbolthay vì stringcho phép các mô-đun khác nhau tạo các thuộc tính không xung đột với nhau. Symbolscũng có thể được đặt ở chế độ riêng tư để những người không có quyền truy cập trực tiếp vào tài sản của họ không thể truy cập được Symbol.

Symbolslà một nguyên thủy mới . Cũng giống như number, stringbooleannguyên thủy, Symbolcó một chức năng mà có thể được sử dụng để tạo ra chúng. Không giống như các nguyên thủy khác, Symbolskhông có cú pháp bằng chữ (ví dụ như string'') - cách duy nhất để tạo chúng là với hàm Symboltạo theo cách sau:

let symbol = Symbol();

Trong thực tế, Symbolchỉ là một cách hơi khác để gắn các thuộc tính vào một đối tượng - bạn có thể dễ dàng cung cấp các Symbolsphương thức chuẩn được biết đến như là phương thức Object.prototype.hasOwnPropertyxuất hiện trong mọi thứ kế thừa từ đó Object.

Dưới đây là một số lợi ích của Symbolloại nguyên thủy.

Symbols có khả năng sửa lỗi được xây dựng trong

Symbols có thể được cung cấp một mô tả, mà thực sự chỉ được sử dụng để gỡ lỗi để làm cho cuộc sống dễ dàng hơn một chút khi đăng nhập chúng vào bảng điều khiển.

Symbolscó thể được sử dụng làm Objectchìa khóa

Đây là nơi Symbolthực sự thú vị. Chúng được đan xen rất nhiều với các đồ vật. Symbolcó thể được chỉ định làm khóa cho các đối tượng, nghĩa là bạn có thể gán số lượng duy nhất không giới hạn Symbolcho một đối tượng và được đảm bảo rằng những khóa này sẽ không bao giờ xung đột với stringkhóa hoặc duy nhất khác Symbols.

Symbols có thể được sử dụng như một giá trị duy nhất.

Giả sử bạn có một thư viện khai thác gỗ, trong đó bao gồm nhiều cấp độ log như logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARNvà vân vân. Trong mã ES5, bạn muốn tạo các strings (so logger.levels.DEBUG === 'debug') hoặc numbers ( logger.levels.DEBUG === 10). Cả hai đều không lý tưởng vì những giá trị đó không phải là giá trị duy nhất, nhưng Symbollà! Vì vậy, logger.levelsđơn giản trở thành:

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

Đọc thêm trong bài viết tuyệt vời này .


10
Tôi không chắc tôi hiểu ví dụ của bạn, và tại sao bạn cần log.levels = {DEBUG: Symbol('debug')và không chỉ đơn giản log.levels = {DEBUG:'debug'}. cuối cùng thì nó cũng vậy Tôi nghĩ rằng đáng để đề cập rằng các Biểu tượng là vô hình khi lặp qua các phím của Đối tượng. đó là "thứ" của họ
vsync

Một lợi ích là ai đó không thể vô tình sử dụng một nghĩa đen và tin rằng nó sẽ hoạt động mãi mãi. (Lưu ý rằng đây không phải là một đối số thực sự mạnh mẽ, vì người ta có thể chỉ cần sử dụng {}và đạt được kết quả tương tự (như giá trị duy nhất) hoặc có thể một nghĩa đen được ưu tiên trong dự án đó hoặc bạn có thể nói rằng trước tiên bạn cần đọc tài liệu.) Tôi cá nhân nghĩ rằng nó cung cấp khả năng đọc tốt về ý nghĩa độc đáo trong mã
apple apple

lưu ý khi được sử dụng làm giá trị duy nhất, đối tượng theo nghĩa đen cũng có khả năng sửa lỗi được xây dựng tức là Symbol("some message")trở thành {message:'some message'}, đối tượng được cho là làm tốt hơn ở đây khi bạn có thể thêm nhiều trường.
táo táo

38

Bài đăng này là về Symbol(), được cung cấp với các ví dụ thực tế tôi có thể tìm / tạo và các sự kiện & định nghĩa tôi có thể tìm thấy.

TLDR;

Đây Symbol()là kiểu dữ liệu, được giới thiệu cùng với việc phát hành ECMAScript 6 (ES6).

Có hai sự thật gây tò mò về Biểu tượng.

  • kiểu dữ liệu đầu tiên và kiểu dữ liệu duy nhất trong JavaScript không có nghĩa đen

  • bất kỳ biến nào, được xác định bằng Symbol(), đều có nội dung độc đáo, nhưng nó không thực sự riêng tư .

  • bất kỳ dữ liệu nào cũng có Biểu tượng riêng và đối với cùng một dữ liệu, các Biểu tượng sẽ giống nhau . Thông tin thêm trong đoạn văn sau, nếu không thì đó không phải là TLRD; :)

Làm thế nào để tôi khởi tạo biểu tượng?

1. Để có một mã định danh duy nhất có giá trị gỡ lỗi

Bạn có thể làm theo cách này:

var mySymbol1 = Symbol();

Hoặc theo cách này:

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

Các "some text here"chuỗi không thể được chiết xuất từ các biểu tượng, nó chỉ là một mô tả cho mục đích gỡ lỗi. Nó không thay đổi hành vi của biểu tượng theo bất kỳ cách nào. Mặc dù, bạn có thể làm console.logđiều đó (điều này là công bằng, vì giá trị là để gỡ lỗi, để không nhầm lẫn nhật ký đó với một số mục nhật ký khác):

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

2. Để có được một biểu tượng cho một số dữ liệu chuỗi

Trong trường hợp này, giá trị của biểu tượng thực sự được tính đến và theo cách này, hai biểu tượng có thể không phải là duy nhất.

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

Chúng ta hãy gọi những biểu tượng đó là "biểu tượng loại hai". Chúng không giao nhau với các ký hiệu "loại thứ nhất" (nghĩa là các ký hiệu được xác định bằng Symbol(data)) theo bất kỳ cách nào.

Hai đoạn tiếp theo chỉ liên quan đến biểu tượng loại đầu tiên .

Làm cách nào để tôi hưởng lợi từ việc sử dụng Biểu tượng thay vì các loại dữ liệu cũ hơn?

Trước tiên chúng ta hãy xem xét một đối tượng, một kiểu dữ liệu tiêu chuẩn. Chúng ta có thể định nghĩa một số cặp khóa-giá trị ở đó và có quyền truy cập vào các giá trị bằng cách chỉ định khóa.

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

Nếu chúng ta có hai người với tên Peter thì sao?

Làm điều này:

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

sẽ không có ý nghĩa nhiều.

Vì vậy, dường như là một vấn đề của hai người hoàn toàn khác nhau có cùng tên. Sau đó hãy tham khảo mới Symbol(). Nó giống như một người trong cuộc sống thực - bất kỳ người nào cũng là duy nhất , nhưng tên của họ có thể bằng nhau. Hãy xác định hai "người".

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

Bây giờ chúng tôi đã có hai người khác nhau có cùng tên. Là người của chúng ta thực sự khác nhau? Họ đang; bạn có thể kiểm tra điều này:

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

Làm thế nào để chúng ta có lợi ở đó?

Chúng tôi có thể tạo hai mục trong đối tượng của bạn cho những người khác nhau và họ không thể bị nhầm lẫn theo bất kỳ cách nào.

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

Lưu ý:
Tuy nhiên, đáng chú ý là việc xâu chuỗi đối tượng JSON.stringifysẽ bỏ tất cả các cặp được khởi tạo với Biểu tượng làm khóa.
Thực hiện Object.keyssẽ không trả lại các Symbol()->valuecặp như vậy .

Sử dụng khởi tạo này, hoàn toàn không thể nhầm lẫn các mục nhập cho người thứ nhất và người thứ hai. Gọi console.logcho họ sẽ xuất chính xác tên thứ hai của họ.

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

Khi được sử dụng trong đối tượng, nó khác như thế nào so với việc xác định thuộc tính không thể đếm được?

Thật vậy, đã có một cách để xác định một tài sản được ẩn Object.keysvà liệt kê. Đây là:

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

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

Điều gì khác biệt Symbol()mang lại ở đó? Sự khác biệt là bạn vẫn có thể nhận được thuộc tính được xác định Object.definePropertytheo cách thông thường:

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

Và nếu được định nghĩa bằng Biểu tượng như trong đoạn trước:

fruit = Symbol("apple");

Bạn sẽ có khả năng nhận giá trị của nó chỉ khi biết biến của nó, tức là

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

Hơn nữa, việc xác định một thuộc tính khác dưới khóa "apple"sẽ làm cho đối tượng bỏ đi thuộc tính cũ hơn (và nếu được mã hóa cứng, nó có thể gây ra lỗi). Vì vậy, không còn táo! Thật tiếc. Nhắc đến đoạn trước, các Biểu tượng là duy nhất và xác định một khóa Symbol()sẽ làm cho nó trở nên duy nhất.

Loại chuyển đổi và kiểm tra

  • Không giống như các loại dữ liệu khác, không thể chuyển đổi Symbol()sang bất kỳ loại dữ liệu nào khác.

  • Có thể "tạo" một biểu tượng dựa trên kiểu dữ liệu nguyên thủy bằng cách gọi Symbol(data).

  • Về mặt kiểm tra loại, không có gì thay đổi.

    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


Điều này đã được di chuyển từ Tài liệu SO?
Knu

1
@KNU không phải vậy; Tôi đã thu thập thông tin và tự viết câu trả lời này
nicael

Câu trả lời thực sự đẹp!
Mihai Alexandru-Iovy

1
Câu trả lời tuyệt vời trên Symbol, tuy nhiên tôi vẫn không biết tại sao tôi sẽ sử dụng đối tượng với các phím biểu tượng thay vì một mảng. Nếu tôi có nhiều người như {"peter": "pan"} {"john": "doe"} thì thật tệ khi tôi đặt họ vào một đối tượng. Vì lý do tương tự như tôi không tạo các lớp với các thuộc tính trùng lặp như personFirstName1, personFirstName2. Điều này kết hợp với việc không thể xâu chuỗi nó, tôi không thấy lợi ích chỉ là nhược điểm.
eldo

18

Đây là cách tôi nhìn thấy nó. Các biểu tượng cung cấp 'mức độ riêng tư cao hơn', bằng cách ngăn các khóa / thuộc tính của đối tượng không bị lộ thông qua một số phương thức phổ biến như Object.keys () và JSON.opesify ().

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…)

Mặc dù được cung cấp một đối tượng, các thuộc tính như vậy vẫn có thể được hiển thị thông qua sự phản chiếu, proxy, Object.getOwnPropertySymbols (), v.v., không có phương tiện tự nhiên nào để truy cập chúng thông qua một vài phương thức trực tiếp, đôi khi có thể đủ từ quan điểm OOP.


2

Biểu tượng JS là một kiểu dữ liệu nguyên thủy mới. Chúng là các mã thông báo đóng vai trò là ID duy nhất . Một biểu tượng có thể được tạo bằng cách sử dụng hàm Symboltạo. Ví dụ đoạn trích này từ 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.

Nó thường thuận tiện để sử dụng các biểu tượng làm khóa thuộc tính đối tượng duy nhất, ví dụ:

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

Biểu tượng có hai trường hợp sử dụng chính:

  1. Thuộc tính đối tượng ẩn giấu. Nếu chúng ta muốn thêm một thuộc tính vào một đối tượng mà thuộc về một tập lệnh hoặc thư viện khác, chúng ta có thể tạo một biểu tượng và sử dụng nó làm khóa thuộc tính. Một thuộc tính tượng trưng không xuất hiện for..in, vì vậy nó sẽ không được xử lý ngẫu nhiên cùng với các thuộc tính khác. Ngoài ra, nó sẽ không được truy cập trực tiếp, bởi vì tập lệnh khác không có biểu tượng của chúng tôi. Vì vậy, tài sản sẽ được bảo vệ khỏi việc sử dụng ngẫu nhiên hoặc ghi đè.

    Vì vậy, chúng ta có thể có một cách tình cờ, giấu một thứ gì đó vào các đối tượng mà chúng ta cần, nhưng những người khác không nên nhìn thấy, sử dụng các thuộc tính tượng trưng.

  2. Có nhiều biểu tượng hệ thống được sử dụng bởi JavaScript có thể truy cập như Symbol.*. Chúng ta có thể sử dụng chúng để thay đổi một số hành vi tích hợp. Chẳng hạn, ...... Symbol.iteratorđối với các lần lặp, Symbol.toPrimitiveđể thiết lập chuyển đổi từ đối tượng sang nguyên thủy, v.v.

Nguồn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.