更新:最近Mozilla发布了一篇精彩的文章。如果你好奇,就读读吧。

正如你可能知道的,他们计划在ECMAScript 6中包括新的Symbol原始类型(更不用说其他一些疯狂的东西)。我一直认为:符号概念在Ruby中是不必要的;我们可以很容易地使用纯字符串,就像在JavaScript中做的那样。现在他们决定让JS中的事情变得复杂。

我不明白他们的动机。有人能解释一下JavaScript中是否真的需要符号吗?


当前回答

这篇文章是关于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将删除所有以Symbol作为键初始化的对。 执行对象。键也不会返回这样的Symbol()->值对。

使用这种初始化,绝对不可能将条目误认为第一人称和第二人称。为它们调用console.log将正确地输出它们的第二个名称。

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

在对象中使用时,与定义不可枚举属性相比有何不同?

事实上,已经存在一种方法来定义一个属性,以隐藏在Object中。键和枚举。下面就是:

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)来基于基本数据类型“制造”一个符号。 在检查类型方面,没有任何变化。 函数isSymbol(变量){ return typeof someSymbol === "symbol"; } var a_Symbol = Symbol("嘿!"); var totaly_not_a_symbol = "hey"; console.log (isSymbol (a_Symbol));/ /正确的 console.log (isSymbol (totally_Not_A_Symbol));/ /错误

其他回答

符号不能保证真正的隐私,但可以用来区分对象的公共属性和内部属性。让我们举一个例子,在这个例子中我们可以使用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类属性类型不是私有的。为了使它私有,我们必须创建一个闭包。下面的示例说明了如何使用闭包将类型设置为私有。

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实例引入了一个额外的闭包,这可能会损害性能。

现在我们介绍符号。这可以帮助我们在不使用额外不必要的闭包的情况下使属性为私有。代码示例如下:

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

这篇文章是关于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将删除所有以Symbol作为键初始化的对。 执行对象。键也不会返回这样的Symbol()->值对。

使用这种初始化,绝对不可能将条目误认为第一人称和第二人称。为它们调用console.log将正确地输出它们的第二个名称。

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

在对象中使用时,与定义不可枚举属性相比有何不同?

事实上,已经存在一种方法来定义一个属性,以隐藏在Object中。键和枚举。下面就是:

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)来基于基本数据类型“制造”一个符号。 在检查类型方面,没有任何变化。 函数isSymbol(变量){ return typeof someSymbol === "symbol"; } var a_Symbol = Symbol("嘿!"); var totaly_not_a_symbol = "hey"; console.log (isSymbol (a_Symbol));/ /正确的 console.log (isSymbol (totally_Not_A_Symbol));/ /错误

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

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

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

这种强烈的动机是否足以为语言添加符号还有待商榷。

以下是我的看法。通过防止对象的键/属性通过一些流行的方法(如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的角度来看,这些方法有时已经足够了。

符号是一种新的特殊类型的对象,可以用作对象中的唯一属性名。使用符号而不是字符串允许不同的模块创建互不冲突的属性。符号也可以有效地设置为私有,这样它们的属性就不能被尚未直接访问符号的任何人访问。

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

let symbol = Symbol();

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

下面是Symbol原语类型的一些好处。

符号具有内建的可调试性

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

符号可以用作对象键

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

符号可以用作唯一值

让我们假设您有一个日志库,其中包括多个日志级别,如logger.levels。调试logger.levels。INFO, logger。levels。warn等等。在ES5代码中,你需要创建这些字符串(logger.levels. debug === 'debug')或数字(logger.levels. debug == 'debug')。Debug === 10)。这两种方法都不理想,因为这些值不是唯一的值,但符号是唯一的值!所以日志记录器。Levels简单地变成:

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

阅读这篇很棒的文章。