更新:最近出现了一篇来自 Mozilla的精彩文章。如果您好奇,请阅读它。
您可能知道他们计划在 ECMAScript 6 中包含新的 Symbol 原始类型(更不用说其他一些疯狂的东西)。我一直认为:symbol
Ruby中的概念是不必要的;我们可以轻松地使用普通字符串代替,就像我们在 JavaScript 中所做的那样。现在他们决定让 JS 中的事情复杂化。
我不明白动机。有人可以向我解释我们是否真的需要 JavaScript 中的符号吗?
更新:最近出现了一篇来自 Mozilla的精彩文章。如果您好奇,请阅读它。
您可能知道他们计划在 ECMAScript 6 中包含新的 Symbol 原始类型(更不用说其他一些疯狂的东西)。我一直认为:symbol
Ruby中的概念是不必要的;我们可以轻松地使用普通字符串代替,就像我们在 JavaScript 中所做的那样。现在他们决定让 JS 中的事情复杂化。
我不明白动机。有人可以向我解释我们是否真的需要 JavaScript 中的符号吗?
将符号引入 Javascript 的最初动机是启用私有属性。
不幸的是,它们最终被严重降级。它们不再是私有的,因为您可以通过反射找到它们,例如,使用Object.getOwnPropertySymbols
或 代理。
它们现在被称为唯一符号,它们唯一的用途是避免属性之间的名称冲突。例如,ECMAScript 本身现在可以通过某些方法引入扩展钩子,您可以将这些方法放在对象上(例如定义它们的迭代协议),而不会冒着与用户名冲突的风险。
这是否足以成为在语言中添加符号的动机是有争议的。
符号不能保证真正的隐私,但可用于分隔对象的公共属性和内部属性。让我们举一个例子,我们可以用它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
符号是一种新的特殊对象,可用作对象中的唯一属性名称。使用符号代替字符串允许不同的module创建互不冲突的属性。符号也可以有效地私有,这样任何没有直接访问符号的人都无法访问它们的属性。
符号是一种新的原语,就像数字、字符串和布尔原语一样。与其他原语不同,符号没有文字语法(例如 how string
has ''
)——创建它们的唯一方法是通过Symbol
以下方式使用构造函数:
let symbol = Symbol();
实际上,符号只是将属性附加到对象的一种稍微不同的方式——您可以轻松地提供众所周知的符号作为标准方法,就像Object.prototype.hasOwnProperty
出现在继承自 的所有内容中一样Object
。
以下是Symbol
原始类型的一些好处。
符号可以给出描述,它实际上只是用于调试,以便在将它们记录到控制台时使生活更轻松。
这是符号变得非常有趣的地方。它们与物体紧密相连。符号可以作为对象的键分配,这意味着您可以为对象分配无限数量的唯一符号,并确保这些符号永远不会与字符串键或其他唯一符号发生冲突。
让我们假设你有一个日志库,其中包括多个日志级别,例如logger.levels.DEBUG
,logger.levels.INFO
,logger.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');
这篇文章是关于Symbol()
,提供了我可以找到/制作的实际示例以及我可以找到的事实和定义。
的Symbol()
是数据类型,与ECMAScript的6(ES6)的版本中引入的。
关于 Symbol 有两个奇怪的事实。
JavaScript 中的第一个数据类型,也是唯一一个没有文字的数据类型
任何用 定义的变量Symbol()
都会获得独特的内容,但它并不是真正的私有。
任何数据都有自己的符号,对于相同的数据,符号将是相同的。下一段中的更多信息,否则它不是 TLRD;:)
你可以这样做:
var mySymbol1 = Symbol();
或者这样:
var mySymbol2 = Symbol("some text here");
该"some text here"
字符串不能从符号中提取,它只是用于调试目的的描述。它不会以任何方式改变符号的行为。虽然,您可以console.log
这样做(这是公平的,因为该值用于调试,以免将该日志与其他日志条目混淆):
console.log(mySymbol2);
// Symbol(some text here)
在这种情况下,实际上考虑了符号的值,这样两个符号可能是非唯一的。
var a1 = Symbol.for("test");
var a2 = Symbol.for("test");
console.log(a1 == a2); //true!
让我们称这些符号为“第二类”符号。它们不会Symbol(data)
以任何方式与“第一类”符号(即用 定义的符号)相交。
接下来的两段仅涉及第一种符号。
让我们首先考虑一个对象,一种标准数据类型。我们可以在那里定义一些键值对,并通过指定键来访问这些值。
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
这是我的看法。符号通过防止对象的键/属性通过一些流行的方法(例如 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 的角度来看,这可能就足够了。