将符号引入ES6的动机是什么?


368

更新:最近有来自Mozilla精彩文章出现。好奇的话请阅读。

如您所知,他们计划在ECMAScript 6中包括新的Symbol原语类型(更不用说其他疯狂的东西了)。我一直认为:symbolRuby 中的这个概念是不必要的。我们可以像使用JavaScript一样轻松地使用纯字符串。现在,他们决定将JS中的内容复杂化。

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


6
我不知道这种解释的真实性,但这只是一个开始:tc39wiki.calculist.org/es6/symbols
Felix Kling 2014年

8
符号启用了很多功能,它们允许对象上具有范围的唯一标识符。例如,对只能在一处访问的对象具有属性。
本杰明·格林巴姆2014年

5
不确定,因为您可以使用Object.getOwnPropertySymbols(o)
Yanis 2014年

4
它是唯一性,而不是隐私。
澳航94重型

2
他们将要使用privatepublic类属性关键字来实现更复杂的类实现,因此他们决定放弃该类以实现更简单的类实现。代替this.x = x你应该做的public x = x和私有变量private y = y。他们决定放弃该方法,以实现最少的类实现。然后,Symbol将成为在最小实现中获取私有属性的必需解决方法。
lyschoening 2014年

Answers:


224

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

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

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

是否足够强大,是否有动机在语言中添加符号尚有争议。


93
大多数语言(所有主流语言均为afaik)都提供了某种机制(通常是反射)来获得对私有语言的访问。
Esailija 2014年

19
@Esailija,我认为这是不对的-特别是因为许多语言一开始都不提供反思。通过反射泄漏私有状态(例如在Java中)应该被认为是错误,而不是功能。在网页上尤其如此,在网页上具有可靠的私有状态可能与安全性相关。当前,在JS中实现它的唯一方法是通过闭包,这既繁琐又昂贵。
Andreas Rossberg

38
该机制不必具有反射性-如果确实需要,C ++,Java,C#,Ruby,Python,PHP,Objective-C都允许一种或另一种方式进行访问。这不是真正的能力,而是沟通。
Esailija 2014年

4
@plalx在网络上,封装有时也与安全性有关。
Andreas Rossberg'3

3
不幸的Object.getOwnPropertySymbols是,@ RolandPihlakas 并不是唯一的漏洞。使用代理拦截对“私有”属性的访问权限的能力就越困难。
安德里亚斯·罗斯伯格

95

符号不能保证真正的私密性,但可以用于分隔对象的公共属性和内部属性。让我们举个例子,我们可以使用它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

以上,Petclass属性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

15
注意符号属性不是私有的!符号无碰撞。您可能需要阅读接受的答案。
Bergi 2015年

3
是的,符号不能保证真正的隐私,但是可以用于分隔对象的公共属性和内部属性。抱歉,忘了将此点添加到我的答案中。将相应地更新我的答案。
Samar Panda

@SamarPanda,您还可以说带前缀的成员_并不能保证真正的隐私,但是可以用来分隔对象的公共属性和内部属性。换句话说,毫无意义的答案。
Pacerier

10
我不会说毫无意义,因为默认情况下符号是无法枚举的,而且“错误”也无法访问它,而其他任何键都可以访问。
帕特里克

5
我发现您的答案是唯一一个真正有意义的示例,它说明了为什么要将对象的私有属性定义为Symbol而不是普通属性。
Luis Lobo Borobia

42

Symbols是一种新的特殊对象,可用作对象中的唯一属性名称。使用Symbol代替代替string允许不同的模块创建彼此不冲突的属性。Symbols也可以设为私有,这样,尚无直接访问的任何人都无法访问其属性Symbol

Symbols是一个新的原语。就像numberstringboolean原语,Symbol有可用于创建它们的功能。不像其他的原语,Symbols没有文字语法(例如,如何string'') -创建它们与唯一的办法Symbol以下列方式构造:

let symbol = Symbol();

实际上,Symbol将属性附加到对象只是一种稍微不同的方式-您可以轻松地提供众所周知的Symbols标准方法,就像Object.prototype.hasOwnProperty它出现在从继承的所有内容中一样Object

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

Symbols 具有内置的可调试性

Symbols 可以给出一个描述,该描述实际上仅用于调试,以便将它们记录到控制台时使工作变得更轻松。

Symbols可以用作Object

这是Symbol真正有趣的地方。它们与对象紧密地交织在一起。Symbol可以指定为对象的键,这意味着您可以为对象分配无限数量的唯一键,Symbol并确保这些string键永远不会与键或其他唯一键冲突Symbols

Symbols 可以用作唯一值。

让我们假设你有一个日志库,其中包括多个日志级别,例如logger.levels.DEBUGlogger.levels.INFOlogger.levels.WARN等等。在ES5代码中,您希望使用这些strings(so logger.levels.DEBUG === 'debug')或numbers(logger.levels.DEBUG === 10)。这两个都不理想,因为这些值不是唯一值,但是Symbols是!因此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');

在这篇伟大的文章中阅读更多内容


10
我不确定我是否理解您的示例,以及为什么需要log.levels = {DEBUG: Symbol('debug')而不是简单地理解log.levels = {DEBUG:'debug'}。最后是一样的。我认为值得一提的是,当迭代对象的键时,符号是不可见的。这就是他们的“东西”
vsync

一项好处是,某人不能意外使用文字并相信它会永远有效。(请注意,这并不是一个很强的论据,因为一个人可以简单地使用{}并获得相同的结果(作为唯一值),或者在该项目中首选文字,或者可以说需要先阅读文档。)我个人认为它在代码中提供了独特含义的良好可读性
apple apple

请注意,当用作唯一值时,对象文字也具有内置的可调试性,Symbol("some message")变成了{message:'some message'},可以说对象在这里表现更好,因为您可以添加多个字段。
苹果苹果

38

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

如果我们有两个叫Peter的人怎么办?

这样做:

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"将使该对象删除较旧的属性(如果进行硬编码,则可能会引发错误)。因此,没有更多的苹果了!真可惜。参考上一段,这些符号是唯一的,并定义了一个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


这是从SO Documentation迁移过来的吗?
Knu

1
@KNU不是;我收集了信息并自己写下了这个答案
nicael

真的很漂亮的答案!
Mihai Alexandru-Ionut

1
关于Symbol的好答案,但是我仍然不知道为什么要使用带符号键的对象而不是数组。如果我有多个人,例如{“ peter”:“ pan”} {“ john”:“ doe”},将我放在一个对象中对我来说是不好的。出于与我不使用具有重复属性(例如personFirstName1,personFirstName2)的类相同的原因。再加上无法对其进行分类,我看不出好处只是缺点。
Eldo

18

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


2

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


0

符号有两个主要用例:

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

    因此,我们可以使用符号属性“隐式”将某些内容隐藏到所需的对象中,而其他对象则看不到。

  2. JavaScript使用了许多系统符号,可以通过来访问Symbol.*。我们可以使用它们来更改某些内置行为。例如,...... Symbol.iterator表示可迭代Symbol.toPrimitive对象,以设置对象到原始的转换等。

资源

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.