将 Symbols 引入 ES6 的动机是什么?

IT技术 javascript symbols ecmascript-harmony ecmascript-6
2021-01-20 11:26:44

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

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

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

6个回答

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

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

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

这是否足以成为在语言中添加符号的动机是有争议的。

@plalx,在网络上,封装有时也与安全有关。
2021-03-29 11:26:44
大多数语言(所有主流语言都是 afaik)提供了一些机制,通常是反射,无论如何都可以访问私有。
2021-04-01 11:26:44
@Esalija,我认为这不是真的——特别是,因为许多语言一开始就没有提供反射。通过反射泄露私有状态(例如在 Java 中)应该被视为一个错误,而不是一个功能。在网页上尤其如此,在网页上拥有可靠的私有状态可能与安全相关。目前,在 JS 中实现它的唯一方法是通过闭包,这既乏味又昂贵。
2021-04-02 11:26:44
该机制不一定是反射——C++、Java、C#、Ruby、Python、PHP、Objective-C 都允许以一种或另一种方式访问​​,如果你真的想要的话。这不是真正的能力,而是沟通。
2021-04-03 11:26:44
不幸的Object.getOwnPropertySymbols,@RolandPihlakas并不是唯一的泄漏;更困难的是使用代理拦截对“私有”财产的访问的能力。
2021-04-07 11:26:44

符号不能保证真正的隐私,但可用于分隔对象的公共属性和内部属性。让我们举一个例子,我们可以用它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
我不会说毫无意义,因为符号默认是不可枚举的,它也不能被“错误”访问,而任何其他键都可以。
2021-03-13 11:26:44
我发现你的答案是唯一一个真正有一个有意义的例子,为什么你想将对象的私有属性定义为符号,而不仅仅是一个普通属性。
2021-03-13 11:26:44
@SamarPanda,您不妨说给成员添加前缀_并不能保证真正的隐私,但可用于分隔对象的公共属性和内部属性。换句话说,毫无意义的回答。
2021-03-24 11:26:44
是的,符号不能保证真正的隐私,但可用于分隔对象的公共属性和内部属性。抱歉,忘了将这一点添加到我的答案中。将相应地更新我的答案。
2021-03-29 11:26:44
请注意,符号属性不是私有的符号是无碰撞的您可能想阅读已接受的答案。
2021-04-01 11:26:44

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

符号是一种新的原语,就像数字、字符串和布尔原语一样。与其他原语不同,符号没有文字语法(例如 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');

在这篇很棒的文章中阅读更多内容

一个好处是有人不会意外使用文字并相信它会永远有效。(请注意,这并不是一个非常有力的论据,因为人们可以简单地使用{}并获得相同的结果(作为唯一值),或者在该项目中可能首选文字,或者您可以说需要先阅读文档。)我个人认为它提供了代码中独特含义的良好可读性
2021-03-27 11:26:44
请注意,当用作唯一值时,对象文字也具有内置的可调试性,Symbol("some message")成为{message:'some message'},可以说对象在这里做得更好,因为您可以添加多个字段。
2021-04-07 11:26:44
我不确定我是否理解你的例子,为什么你需要log.levels = {DEBUG: Symbol('debug')而不是简单的log.levels = {DEBUG:'debug'}. 最后是一样的。我认为值得一提的是,在迭代对象的键时,符号是不可见的。那是他们的“东西”
2021-04-08 11:26:44

这篇文章是关于Symbol(),提供了我可以找到/制作的实际示例以及我可以找到的事实和定义。

TLDR;

Symbol()是数据类型,与ECMAScript的6(ES6)的版本中引入的。

关于 Symbol 有两个奇怪的事实。

  • 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 作为键初始化的对。
执行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"将使对象丢弃旧的(如果硬编码,它可能会引发错误)。所以,不要再吃苹果了!真可惜。参考上一段,Symbols 是唯一的,并且定义了一个 keySymbol()将使其唯一。

类型转换和检查

  • 与其他数据类型不同,不可能将 转换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
    
这是从 SO 文档迁移的吗?
2021-03-23 11:26:44
关于 Symbol 的好答案,但是我仍然不知道为什么要使用带有符号键的对象而不是数组。如果我有多个像 {"peter":"pan"} {"john":"doe"} 这样的人,将它们放在一个对象中对我来说感觉很糟糕。出于同样的原因,我不会创建具有重复属性(如 personFirstName1、personFirstName2)的类。这与无法对其进行字符串化相结合,我不认为好处只是坏处。
2021-03-25 11:26:44
真的很漂亮的答案!
2021-03-30 11:26:44
@KNU 不是;我已经收集了信息并自己写了这个答案
2021-04-10 11:26:44

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