关于javascript:将符号带入ES6的动机是什么?

What is the motivation for bringing Symbols to ES6?

UPDATE: Recently a brilliant article from Mozilla came up. Read it if you're curious.

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

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


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

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

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

这是否足够强大,为语言添加符号的动机是值得商榷的。


符号不保证真正的隐私,但可用于分隔对象的公共和内部属性。让我们举一个例子,我们可以使用Symbol来获得私有属性。

让我们举一个例子,其中对象的属性不是私有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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私有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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。这可以帮助我们将属性设为私有,而无需使用额外的不必要的闭包。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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


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

Symbols是一个新的原语。就像numberstringboolean基元一样,Symbol有一个可用于创建它们的函数。与其他原语不同,Symbols没有文字语法(例如string如何'') - 创建它们的唯一方法是使用Symbol构造函数,方法如下:

1
let symbol = Symbol();

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

以下是Symbol基元类型的一些优点。

Symbols内置了可调试性

Symbols可以给出一个描述,它实际上只是用于调试,以便在将它们登录到控制台时使生活更容易。

Symbols可用作Object

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

Symbols可用作唯一值。

假设您有一个日志库,其中包含多个日志级别,例如logger.levels.DEBUGlogger.levels.INFOlogger.levels.WARN等。在ES5代码中,您需要制作这些string s(所以logger.levels.DEBUG === 'debug')或number s(logger.levels.DEBUG === 10)。这两个都不理想,因为这些值不是唯一值,但Symbol是!所以logger.levels简单地变成:

1
2
3
4
5
6
7
log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');

阅读这篇精彩的文章。


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

TLDR;

Symbol()是数据类型,随ECMAScript 6(ES6)的发布而引入。

关于符号有两个奇怪的事实。

  • 第一个数据类型,只有JavaScript中没有文字的数据类型

  • 任何用Symbol()定义的变量都会获得唯一的内容,但它并不是真正的私有。

  • 任何数据都有自己的符号,对于相同的数据,符号将是相同的。以下段落中的更多信息,否则它不是TLRD; :)

如何初始化符号?

1.获取具有可调试值的唯一标识符

你可以这样做:

1
var mySymbol1 = Symbol();

或者这样:

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

无法从符号中提取"some text here"字符串,它只是用于调试目的的描述。它不会以任何方式改变符号的行为。虽然,你可以console.log它(这是公平的,因为该值是用于调试的,以免错误地将该日志与其他日志条目混淆):

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

2.获取某些字符串数据的符号

在这种情况下,实际上考虑了符号的值,这样两个符号可以是非唯一的。

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

我们将这些符号称为"第二类"符号。它们不以任何方式与"第一类型"符号(即用Symbol(data)定义的符号)相交。

接下来的两段仅涉及第一类符号。

如何使用Symbol代替旧数据类型?

我们首先考虑一个对象,一个标准数据类型。我们可以在那里定义一些键值对,并通过指定键来访问值。

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

如果我们有两个名叫彼得的人??怎么办?

这样做:

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

没有多大意义。

因此,似乎是两个完全不同的人具有相同名称的问题。让我们再引用新的Symbol()。它就像现实生活中的一个人 - 任何人都是独一无二的,但他们的名字可以是平等的。让我们定义两个"人"。

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

现在我们有两个同名的人。我们的人确实不同吗?他们是;你可以检查一下:

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

我们如何在那里受益?

我们可以在您的对象中为不同的人创建两个条目,并且它们不会以任何方式被误解。

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

Note:
It's worth to notice, though, that stringifying the object with JSON.stringify will drop all the pairs initialised with a Symbol as a key.
Executing Object.keys won't either return such Symbol()->value pairs.

使用此初始化,绝对不可能将条目误认为是第一人和第二人。为他们调用console.log将正确输出他们的第二个名字。

1
2
3
4
 console.log(persons[a]);
 // first
 console.log(persons[b]);
 // pan

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

实际上,已经存在一种方法来定义要从Object.keys和枚举隐藏的属性。这里是:

1
2
3
4
5
6
7
var anObject = {};
var fruit ="apple";    

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

Symbol()带来了什么区别?区别在于您仍然可以通过以下方式获取使用Object.defineProperty定义的属性:

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

如果使用符号定义,如上一段所述:

1
fruit = Symbol("apple");

只有知道其变量,您才有能力获得其价值,即

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

此外,在键"apple"下定义另一个属性将使对象删除较旧的属性(如果硬编码,则可能抛出错误)。所以,不再是苹果!这真遗憾。参考前一段,符号是唯一的,并定义一个键,因为Symbol()将使其独特。

键入转换和检查

  • 与其他数据类型不同,不可能将Symbol()转换为任何其他数据类型。

  • 通过调用Symbol(data),可以基于原始数据类型"制作"符号。

  • 在检查类型方面,没有任何变化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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


我就是这样看的。符号通过阻止对象的键/属性通过一些流行的方法(如Object.keys()和JSON.stringify())来提供"额外的隐私级别"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的角度来看,这有时可能就足够了。