401

更新:最近一篇来自 Mozilla 的精彩文章出现了。如果您好奇,请阅读它。

你可能知道他们计划在 ECMAScript 6 中包含新的 Symbol 原始类型(更不用说其他一些疯狂的东西了)。我一直认为:symbolRuby 中的概念是不必要的;我们可以轻松地使用纯字符串,就像我们在 JavaScript 中所做的那样。现在他们决定用 JS 使事情复杂化。

我不明白动机。有人可以向我解释我们是否真的需要 JavaScript 中的符号?

4

7 回答 7

250

将符号引入 Javascript 的最初动机是启用私有属性。

不幸的是,他们最终被严重降级。它们不再是私有的,因为您可以通过反射找到它们,例如使用Object.getOwnPropertySymbols或代理。

它们现在被称为唯一符号,它们的唯一用途是避免属性之间的名称冲突。例如,ECMAScript 本身现在可以通过某些方法引入扩展钩子,您可以将这些方法放在对象上(例如定义它们的迭代协议),而不会冒与用户名冲突的风险。

这是否足以成为在语言中添加符号的动力是值得商榷的。

于 2014-03-09T08:48:31.727 回答
111

符号不保证真正的隐私,但可用于分隔对象的公共属性和内部属性。让我们举一个例子,我们可以使用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
于 2015-04-28T01:19:23.563 回答
51

符号是一种新的、特殊的对象,可以用作对象中的唯一属性名称。使用符号而不是字符串允许不同的模块创建不相互冲突的属性。符号也可以有效地设为私有,这样任何没有直接访问符号的人都无法访问它们的属性。

符号是一种新的原语,就像数字、字符串和布尔原语一样。与其他原语不同,符号没有文字语法(例如 how stringhas '')——创建它们的唯一方法是通过Symbol以下方式使用构造函数:

let symbol = Symbol();

实际上,符号只是将属性附加到对象的一种稍微不同的方式——您可以轻松地提供众所周知的符号作为标准方法,就像Object.prototype.hasOwnProperty,它出现在所有继承自 的东西中Object

以下是Symbol原始类型的一些好处。

符号具有内置的可调试性

可以给符号一个描述,这实际上只是用于调试,以便在将它们记录到控制台时更轻松一些。

符号可以用作对象键

这就是符号变得非常有趣的地方。它们与物体紧密交织在一起。符号可以作为键分配给对象,这意味着您可以为对象分配无限数量的唯一符号,并保证这些符号永远不会与字符串键或其他唯一符号冲突。

符号可以用作唯一值

假设您有一个日志库,其中包括多个日志级别,例如logger.levels.DEBUGlogger.levels.INFOlogger.levels.WARN。在 ES5 代码中,您希望创建这些字符串 (so logger.levels.DEBUG === 'debug') 或数字 ( logger.levels.DEBUG === 10)。这两个都不理想,因为这些值不是唯一值,但符号是!所以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');

在这篇精彩的文章中阅读更多内容。

于 2017-08-19T08:39:30.137 回答
48

这篇文章是关于 的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"};

没有多大意义。

因此,似乎是两个完全不同的人同名的问题。那么让我们参考一下 new 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

如果在上一段中使用 Symbol 定义:

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
    
于 2018-04-02T19:08:13.777 回答
19

这是我的看法。符号通过防止对象的键/属性通过一些流行的方法(如 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 的角度来看这可能就足够了。

于 2017-07-26T12:57:52.130 回答
3

JS 符号是一种新的原始数据类型。它们是用作唯一 ID 的令牌。可以使用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

于 2019-08-16T09:14:03.313 回答
0

符号有两个主要用例:

  1. “隐藏”对象属性。如果我们想将属性添加到“属于”另一个脚本或库的对象中,我们可以创建一个符号并将其用作属性键。符号属性不会出现在 中for..in,因此不会与其他属性一起意外处理。也不会直接访问它,因为另一个脚本没有我们的符号。因此,该属性将受到保护,不会被意外使用或覆盖。

    因此,我们可以使用符号属性“秘密地”将某些东西隐藏到我们需要但其他人不应该看到的对象中。

  2. JavaScript 使用了许多系统符号,它们可以作为Symbol.*. 我们可以使用它们来改变一些内置的行为。例如,...... Symbol.iterator对于迭代,Symbol.toPrimitive设置对象到基元的转换等等。

资源

于 2020-02-21T17:33:37.873 回答