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

pgky5nke  于 2023-04-28  发布在  Java
关注(0)|答案(8)|浏览(135)

更新:最近出现了一个brilliant article from Mozilla。如果你好奇的话就读读吧。

正如你可能知道的,他们计划在ECMAScript 6中包含新的Symbol原语类型(更不用说其他一些疯狂的东西)。我一直认为Ruby中的:symbol概念是不必要的;我们可以很容易地使用普通字符串,就像我们在JavaScript中所做的那样。现在他们决定用它来复杂化JS中的事情。
我不明白动机。有人能解释一下JavaScript中是否真的需要符号吗?

zu0ti5jz

zu0ti5jz1#

将符号引入Javascript的最初动机是启用 private 属性。
不幸的是,他们最终被严重降级。它们不再是私有的,因为您可以通过反射找到它们,例如,使用Object.getOwnPropertySymbols或代理。
它们现在被称为 unique 符号,它们唯一的用途是避免属性之间的名称冲突。例如,ECMAScript本身现在可以通过特定的方法引入扩展钩子,这些方法可以放在对象上(例如:例如,定义它们的迭代协议),而不冒它们与用户名冲突的风险。
这是否足以成为在语言中添加符号的动机是有争议的。

7xllpg7q

7xllpg7q2#

符号并不能保证真正的隐私,但可以用来区分对象的公共属性和内部属性。让我们举一个例子,我们可以使用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
5n0oy7gb

5n0oy7gb3#

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

TLDR;

Symbol()是ECMAScript 6(ES6)发布时引入的数据类型。
关于象征有两个奇怪的事实。

  • JavaScript中第一个也是唯一一个没有文字的数据类型
  • 任何用Symbol()定义的变量都得到唯一的内容,但它不是真正的private
  • 任何数据都有它自己的符号,对于相同的数据,符号将是相同的。更多信息请在下面的段落中,否则它不是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"};

也说不通。
所以,这似乎是两个完全不同的人有相同的名字的问题。然后,让我们引用新的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

如果使用前一段中的符号定义:

fruit = Symbol("apple");

只有在知道它的变量i的情况下,你才有能力接收它的值。e.

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
hjqgdpho

hjqgdpho4#

符号是一种新的、特殊的对象,可用作对象中的唯一属性名称。使用符号而不是字符串允许不同的模块创建彼此不冲突的属性。符号也可以被有效地私有化,这样,任何人都不能直接访问符号。
Symbols是一种新的原语,就像数字、字符串和布尔原语一样。与其他原语不同,符号没有文字语法(例如。例如string是如何拥有''的)-创建它们的唯一方法是使用Symbol构造函数,方法如下:

let symbol = Symbol();

实际上,符号只是将属性附加到对象的一种稍微不同的方式-您可以轻松地提供众所周知的符号作为标准方法,就像Object.prototype.hasOwnProperty一样,它出现在从Object继承的所有内容中。
下面是Symbol基元类型的一些好处。

符号内置可调试性

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

符号可以作为对象键

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

符号可以作为唯一值

假设您有一个日志库,它包含多个日志级别,如logger.levels.DEBUGlogger.levels.INFOlogger.levels.WARN等。在ES 5代码中,你会喜欢使用这些字符串(比如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');

great article中阅读更多。

n3ipq98p

n3ipq98p5#

我是这么看的。符号提供了“额外的隐私级别”,通过防止对象的密钥/属性通过一些流行的方法(如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的Angular 来看,这些方法有时可能就足够了。

t2a7ltrp

t2a7ltrp6#

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
iibxawm4

iibxawm47#

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

  1. JavaScript使用的许多系统符号都可以作为Symbol.*访问。我们可以使用它们来改变一些内置的行为。例如,。..... Symbol.iterator用于可迭代对象,Symbol.toPrimitive用于设置对象到原语的转换,等等。
    来源
cfh9epnr

cfh9epnr8#

以下是我如何描述符号,从我的Angular 来看,以及为什么***我认为它们是无用的垃圾***:

快速说明:

Symbols是JavaScript中内置的、唯一的、enum值的等价物,带有一个“description”字符串,该字符串与Symbol的值和唯一性无关。但行为会发生很大变化,具体取决于您使用的是Symbol()还是Symbol.for()!!!
正如其他人已经指出的:

mySymbol = Symbol("blue");
mySymbol.description //"blue"
mySymbol == mySymbol //true

//Unlike what you might expect...
mySymbol != Symbol("blue")

//So, Symbol() is creating a new Symbol each time.
Symbol("blue") != Symbol("blue")

另请参阅,关于包含符号的对象的枚举:

问题一。..

现在,所有这些对您来说似乎都很好,并且可行,直到您发现Symbol.for()。..:
Symbol.for()静态方法使用给定的键在运行时范围的符号注册表中搜索现有的符号,如果找到就返回它。否则,将在全局符号注册表中使用此键创建一个新符号。

a = Symbol("blue");
a.description //"blue"
b = Symbol("blue");
c = Symbol.for("blue");
c.description //"blue"
d = Symbol.for("blue");

a != b //We already know this one, but then...
a != c //Oh? So they're different. Yep!

//But then maybe it's the most recent...?
b != c //Nope!
b != d //Also nope!

c == d //This is the ONLY permutation that is equivalent.

因此,虽然Symbol()创建了一个具有totally-value-and-unique-inrelevant“description”字符串的Symbol,但Symbol.for()创建了一个具有 globally-mapped“description”字符串的Symbol。但是,因为Symbol()的描述字符串是不相关的,Symbol()Symbol.for()在两个完全独立的世界中运行, 同时使用相同的符号类型和类似的术语 。....!

超级清晰,感谢JavaScript霸主!

更多问题。..

所以为了澄清,这就是为什么符号是无用的。..:

  • 假设你遇到一个Symbol。您必须使用它来确定在运行时使用了哪种符号创建方法:
//Method A: Symbol()
Symbol.keyFor(Symbol("blue")) //undefined

//Method B: Symbol.for()
Symbol.keyFor(Symbol.for("blue")) //blue
  • 如果他们使用了方法A,并且您想使用它,您最好希望您的代码能够访问特定的Symbol引用。..
  • 如果他们使用方法B,你最好希望你的代码库中没有其他代码在使用这个符号字符串,因为它是全局的。..
  • 使用Symbols的主要思想是避免名称/密钥冲突。即在未来代码中。但这比现有的类型好到哪里去了
  • 有些人会回答^这个问题的事实是,符号将不会像其他类型那样被枚举(参见本回答前面的链接)。好吗?这有什么用?只需使用Object.defineProperty()并将enumerable设置为false。Symbols所做的一切都是给语言增加了不必要的复杂性,因为现在你必须使用Reflect.ownKeys()这样的其他东西来访问对象的所有属性。正如MDN所说..:

[Reflect.ownKeys()]是在一次调用中获得所有自己的属性-可枚举和不可枚举、字符串和符号的唯一方法,而无需额外的过滤逻辑。例如,Object.getOwnPropertyNames()获取Reflect.ownKeys()的返回值,并仅过滤到字符串值,而Object.getOwnPropertySymbols()仅过滤到符号值。

  • 他们吹嘘Symbols不能被修改,也不能被分配(因此也不能重新分配)属性(这已经可以用基本的JS字符串或Object.freeze()实现)。...),但这更多的是一个障碍而不是一个功能。例如,您可能希望执行以下操作:
myEnum = [
  { name: "blue" },
  { name: "red" }
];

//Can't do this with Symbols...:
for (let index = 0; index < myEnum.length; ++index) {
  myEnum[index].value = index;
}

/*
  which would turn it into:
  myEnum = [
    { name: "blue", value: 0 },
    { name: "red", value: 1 }
  ];
*/
  • Symbol()在其构造函数中接受其他类型的描述(或者根本不接受)(它不使用new)。..),但它stringifies无论你把它,e。例如:
Symbol({ val: 5 }).description == "[object Object]"

太有用了!

总结

所以,我的建议?不要使用符号,如果你能帮助它。使用基本的{}对象,class es,[]数组,字符串,数字-基本上任何其他-代替。JS没有让枚举变得更容易like they should have,而是添加了这个复杂的、比有用的符号更无用的垃圾。

相关问题