ES6新增 Symbol

发布时间:2023-02-06 16:00

Symbol的诞生?

Symbol的诞生,也就是Symbol存在的意义
之前我们的对象属性的数据类型都是字符串,没有其他的了。所以会导致属性名重复,导致属性值被覆盖的情况。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,在添加的操作就很容易覆盖了原有的方法。所以需要一个独一无二的数据类型来完成这个使命。所以Symbol出来主持大局了。

Symbol的介绍

Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。它是JavaScript中的第七种数据类型,与undefined、null、Number(数值)、String(字符串)、Boolean(布尔值)、Object(对象)并列。

你可以这样创建一个Symbol值:

const a = Symbol();
console.log(a);  //Symbol()

使用Symbol函数可以生成一个Symbol类型的值,但是你不能在调用Symbol时使用new关键字,因为Symbol是基本数据类型,而不是对象。比如下面的写法是错误的:

//报错,Symbol is not a constructor
const a = new Symbol();

1. 唯一性

使用Symbol()创建一个Symbol类型的值并赋值给a变量后,你就得到了一个在内存中独一无二的值。

let a = Symbol('a');
let b = Symbol('a');
console.log(a == b) // fasle

尽管a和b都是使用Symbol()创建出来的,但是它们在内存中看起来却是这样的:

ES6新增 Symbol_第1张图片

实际上,a变量拿到了内存中某块内存的唯一引用(这里所说的引用,其实就是该内存的地址)。如果不借助a变量,你不可能再得到这个地址。因此:

a !== b;  //a和b持有的是两块内存的引用

const c = a;  //手动把a里保存的地址保存在c变量中
a === c;  //c和a现在指向同一块内存,因为它们保存了同样的地址

ES6新增 Symbol_第2张图片

这种行为看似难以理解,但其实它与对象遵循相同的规则,如:

var a = {};
var b = {};

a !== b;  //a和b各自被分配了不同的内存,因此它们保存了不同的地址

//借助变量a,变量c拿到了a指向的那个对象的地址,因此两者相等
var c = a;
a === c;

但是对于同为基本数据类型的字符串来说,它不遵循类似的规则。

比如:

var a = "123";
var b = "123";

a === b;  //返回true。两者在常量区引用同一个字符串

我们首先通过变量a在内存中创建了字符串“123”,然后在不借助变量a的情况下,又通过var b = "123"拿到了对“123”这个字符串的引用,两者指向内存中的同一块内存地址。

ES6新增 Symbol_第3张图片

因此我们说,a无法确保别的变量无法拿到它保存的地址(前提是不通过a)。但是对于var a = Symbol()这样的语句,a变量内保存的值是唯一的,因为除了借助a变量,你永远无法得到a中保存的值。这也是Symbol的本质。

可能很多人比较奇怪,一个Symbol类型的变量里面到底保存了什么呢?

我们看两行代码:

var a = Symbol();

console.log(a);  //Symbol()

我们试图输出a的值,但js引擎输出了Symbol()。显然它不能说明a的值是字符串,因为:

typeof a === "symbol";

所以说如果你想问js引擎a的值是多少,引擎只会告诉你它是一个Symbol类型的值。也就是说,Symbol真正存储了什么并不重要,重要的是它的值永远不会与别的值相等。Symbol的中文释义为“标志,符号”,一个Symbol类型的变量只是为了标记一块唯一的内存而存在的。也正是因为这样,Symbol类型的值不参与运算。

2、数据类型的修饰

有人会好奇Symbol( 'a')里面的参数a又是怎么回事呢?字符串a表示一种修饰,对你当前创建的Symbol类型的一种修饰,作为区分使用,否则当你创建多个Symbol数据时,容易混淆。

3.与其他数据类型之间的转换

Symbol不能用四则运算进行操作,否则报错。它只能用显示的方式转为字符串和布尔值,即:String(Symbol())/    Boolean(Symbol())

4. 作为对象的属性 

作为对象属性时,要注意用以下三种方式来书写

let msMymbol = Symbol();

// 第一种写法
let a = {};
a{mySymbol} = 'Hello'


// 第二种写法
let a = {
[mySymbol]:'Hello';
}


// 第三种写法
let a = {}
Object.defineProperty(a,mySymbol,{value:'Hello'})

// 以上的都可以得到 a[mySymbol]  // 'hello'

5、Symbol属性的遍历

以上说了对象属性的创建,但是我们要格外的注意,Symbol作为属性名,该属性不会出现在for...in、for...of 循环中,也不会被Object.keys、Object.getOwnPropertyNames()、JSON.ownKeys(),这个方法就可以返回对象所有的属性,也就是字符串属性和Symbol属性。

但该属性并不是私有属性,它可以被专门的Object.getOwnPropertySymbols()方法遍历出来。该方法返回一个数组,包含了当前对象的所有用作属性名的Symbol值:

var s1 = Symbol('a');
var s2 = Symbol('b');

var a = {
    name: "江左梅郎",
    [s1]: 24,
    [s2]: function(){}
}

var s = Object.getOwnPropertySymbols(a); //[Symbol(a), Symbol(b)]
a[s[0]] = 24; //返回的数组元素不是字符串,而是实际的Symbol值,
               //因此可以通过它引用到对象的该属性

因此遍历该方法的返回值即可遍历所有的Symbol属性。

另外,ES6新增的Reflect.ownKeys()方法可以遍历出所有的常规键名和Symbol键名。语法为:

Reflect.ownKeys(a); //["name", Symbol(a), Symbol(b)]

6. Symbol.for(),Symbol.keyFor()

有时,我们希望重新使用同一个 Symbol 值,以上我们都说了 Symbol 数据类型唯一的,所以只要用 Symbol() 方法创建的 Symbol 类型是无法实现的。所以我们都可以用Symbol.for() 这个方法来实现的。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

console.log(s1 === s2)  // true

注意:这里的Symbol.for() 和 Symbol() 创建的都是Symbol 类型,但是他们的创建机制有所不同,Symbol.for('a') 的创建方式会在创建之前在全局中寻找,有没有用Symbol.for() 的方式,并且key是 ’a' 的字符串创建了 Symbol类型(创建了就会在全局中登记);如果有则不重复创建(已登记的)。然而Symbol('a') 的创建是不会去检索全局的,是直接创建一个新的Symbol类型。这也是用Symbol('a') 创建的两个 Symbol 类型不相等的根本原因。

Symbol.keyFor() 方法返回一个已登记的 Symbol 类型值的 key

let s1 = Symbol.for('foo');
console.log(Symbol.keyFor(s1)) // "foo"

let s2 = Symbol('foo');
console.log(Symbol.keyFor(s2)) // undefined

上面代码中,变量s2 属于未登记的Symbol值,所以返回undefined。也就是Symbol.keyFor() 这个方法,主要服务于Symbol.for() 的。因为Symbol() 方法创建的值用Symbol.keyFor() 永远是undefined

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号