ES6之Symbol
ES6(ECMAScript 2015)中,Symbol
是一种新的原始数据类型,它是JavaScript语言的第七种数据类型,用于生成独一无二的值。
Symbol
的主要目的是为了解决对象属性名可能产生的冲突问题,因为它确保每个由Symbol
创建的值都是唯一的。
Symbol的特点包括:
- 唯一性:每个通过
Symbol
创建的值都是独一无二的,即使传入相同的描述字符串,也会得到不同的Symbol值。 - 不可变性:一旦创建,Symbol值就不能改变,也不能与其他数据类型进行算术或逻辑运算。
- 不可枚举性:Symbol作为对象的键时,不会被常规的
for...in
循环遍历到,也不会出现在Object.keys()
、Object.getOwnPropertyNames()
返回的结果中,但可以通过Object.getOwnPropertySymbols()
获取。 - 非字符串键:Symbol可以作为对象的属性键,这样的属性不能用
.
操作符访问,必须用方括号[]
语法访问。 - 可选描述:创建Symbol时可以传入一个描述字符串,这个字符串主要是为了开发人员在调试时理解Symbol的用途,但不影响Symbol的唯一性。
使用场景及举例说明:
私有属性:
在对象中使用Symbol作为属性名,可以创建实际上的“私有”属性,因为外界无法通过常规手段遍历或猜测这些属性的存在。
const mySymbol = Symbol();
const obj = {
[mySymbol]: 'This is a private property',
};
console.log(obj[mySymbol]); // 输出: This is a private property
防止属性名冲突:
在大型项目或库中,使用Symbol可以避免因不小心重用属性名而引起的冲突。
const idSym = Symbol('id');
const user = {
name: 'Alice',
[idSym]: 123,
};
// 即使有其他代码也使用"id"作为属性名,也不会与[idSym]冲突
内置Symbol用途:
ES6还定义了一些内置的Symbol值,如Symbol.iterator
用于实现迭代协议,让对象可迭代。
let iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
if (step < 2) {
step++;
return { value: step, done: false };
} else {
return { done: true };
}
}
};
}
};
for (let num of iterable) {
console.log(num); // 输出: 1, 2
}
通过上述例子可以看出,Symbol
为JavaScript提供了更强大的对象属性管理和防止名称冲突的能力,特别适用于库开发和需要维护内部状态的场景。
详细说明
ES5困境:容易造成属性名的冲突
ES5 的对象属性名都是字符串,这容易造成属性名的冲突
。
比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入 Symbol 的原因。
Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。
它是JavaScript 语言的第七种数据类型,前六种是:
- undefined 、
- null 、
- 布尔值(Boolean)、
- 字符串(String)、
- 数值(Number)、
- 对象(Object)
- 对象
- 数组
- 函数
- 正则
Symbol 值通过 Symbol 函数生成。
这就是说,对象的属性名现在可以有两种类型:
- 一种是原来就有的字符串
- 另一种就是新增的 Symbol 类型。
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol是一种类似于字符串的数据类型
Symbol 函数前不能使用 new 命令,否则会报错。
这是因为生成的 Symbol 是一个原始类型的值,不是对象。
也就是说,由于 Symbol值不是对象,所以不能添加属性。
基本上,它是一种类似于字符串的数据类型。
Symbol的作用
上面我们说到,Symbol不能与其他数据类型进行运算,那么新增这样一种数据类型有什么用呢?
举个例子:
//文件A.js
var a = {
name: "夕山雨",
getName(){
return this.name;
}
}
exports default a;
//文件B.js
var a = require("A.js");
a.getName = function(){
return "xxx";
}
由于getName这个键本质上只是个字符串,而无论在哪个模块或作用域内,都可以直接引用到“getName”这个字符串,因此字符串类型的属性 很容易被意外覆盖。
但是如果a中的属性是使用Symbol类型的变量作为键,那么它就无法被篡改:
//模块A.js
var s = Symbol();
var a = {
name: "夕山雨",
//s是个变量,因此需要用中括号包裹起来
[s]: function(){
return this.name;
}
}
exports default a;
//模块B.js
var a = require("A.js");
var s = Symbol();
a[s] = function(){
... //它不会对A模块中的[s]属性造成任何影响,因为两个模块的[s]不是同一个属性
}
我们使用一个Symbol类型的变量作为对象属性的键。由于s是一个变量,而不是字符串,因此需要使用中括号括起来(否则它会被当做字符串对待)。
在模块B中我们使用同样的语句var s = Symbol();创建了一个同名变量s,“企图”通过为a[s]重新赋值覆盖a对象上原来的[s]属性,但这并不能生效,因为模块A中的变量s和模块B中的变量s是各自独立的Symbol,他们并不相等,因此无法覆盖。
根本原因在任何情况下都满足:
"getName" === "getName"
//而
Symbol() !== Symbol() //该行为类似{} !== {}
通过把对象的属性的键值设置为Symbol类型,我们有效避免了对象属性被修改,在模块化开发中,对象本身也就更安全。
扩展:JavaScript 语言的数据类型
JavaScript 语言是一种动态类型、弱类型、基于原型和多范式的编程语言。它支持以下几种主要的数据类型:
-
原始(基本)数据类型:
- 字符串(String):用于表示文本,用单引号(’ ')或双引号(" ")包围。
- 数字(Number):用于表示整数和浮点数,JavaScript 中没有单独的整数类型,所有数字都是浮点数。
- 布尔值(Boolean):表示逻辑上的真(true)和假(false)。
- undefined:表示一个变量已被声明但未被赋值时的默认值。
- null:表示一个空对象指针,也可以视为代表“无值”的特殊值。
- Symbol(ES6 引入):一种唯一的、不可变的数据类型,常用于对象的键,以避免属性名的冲突。
-
对象(Object):
- 对象字面量:由花括号{}包围的键值对集合。
- 数组(Array):一种特殊的对象,用于存储多个值的有序集合,元素可以是任意类型。
- 函数(Function):在 JavaScript 中,函数是一等公民,既是对象也是可以赋值给变量、作为参数传递和作为其他函数的返回值。
- 日期(Date):用于处理日期和时间。
- 正则表达式(RegExp):用于匹配字符串中的模式。
- Error对象:用于表示错误信息。
- Math对象:提供数学相关的属性和方法。
- 更多内置对象和自定义对象:如Map、Set、WeakMap、WeakSet等(ES6引入),以及用户可以通过构造函数创建的自定义对象。
-
特殊类型:
- BigInt(ES2020 引入):用于表示大于
2^53 - 1
的整数,解决大整数的精确表示问题。
- BigInt(ES2020 引入):用于表示大于
在JavaScript中,可以使用 typeof
操作符来检测非对象类型的值,而 Object.prototype.toString.call()
方法可以更准确地检测任何类型,包括对象。
原始数据类型
Undefined: 代表未定义的值。当一个变量被声明但没有被赋予任何值时,它的值就是 undefined
。
let x;
console.log(x); // 输出: undefined
Null: 代表空值或者有意设置为无值的对象。它是 undefined
类型的一个特殊值。
let y = null;
console.log(y); // 输出: null
Boolean: 逻辑类型,只有两个值:true
和 false
。
let isTrue = true;
console.log(isTrue); // 输出: true
Number: 表示整数和浮点数。JavaScript 中的数字都是64位浮点数。
let num = 42;
console.log(num); // 输出: 42
String: 用于表示文本,可以使用单引号或双引号包裹。
let text = "Hello, world!";
console.log(text); // 输出: Hello, world!
Symbol (ES6引入): 一种唯一的、不可变的数据类型,通常用作对象的唯一属性键。
let sym = Symbol();
console.log(sym); // 输出: (一个独特的Symbol值)
BigInt (ES2020引入): 用于表示任意精度的整数,超出 Number
类型的范围。
let bigInt = BigInt("9007199254740991");
console.log(bigInt); // 输出: 9007199254740991n
对象类型
Object: 包括数组、函数、日期等,是复合数据类型,可以包含多个值。
- Array: 有序的值集合。
let arr = [1, "text", true]; console.log(arr); // 输出: [1, "text", true]
- Function: 用于执行特定任务的可复用代码块。
let func = function() { return "Hello"; }; console.log(func()); // 输出: Hello
- Date: 用来处理日期和时间。
let now = new Date(); console.log(now); // 输出: 当前日期和时间
- Regular Expression: 用于文本匹配。
let regex = /\d+/; // 匹配一个或多个数字 console.log("123abc".match(regex)); // 输出: ["123"]
- Other Objects: 自定义对象、Map、Set、WeakMap、WeakSet等。
每种数据类型都有其独特的特性和用途,理解这些数据类型是JavaScript编程的基础。