Symbol的简单学习

基本

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 的第七种数据类型。

Symbol 值通过 Symbol 函数生成。Symbol 函数前面不能使用 new 命令,否则会报错。

这是因为生成的 Symbol 是一种原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。

但是为什么不能 new 呢?百度找到一种解释:这样做是为了避免创建 Symbol 原始值包装类型

在引用类型中,有三种原始值包装类型:String、Number、Boolean。原始值类型 "abc"、123 不是对象,原始值包装类型是用来把原始值包装成对象的引用类型。

除了 JS 自身内部处理可能会使用外,我们在开发的实际场景中,一般都不会使用。一般使用字面量的形式,不会创建对应的对象。对开发体验来讲是比较鸡肋的功能。新增的 Symbol、BigInt 没有必要向开发者提供创建对应原始值对象的 API。一般也不会使用,多此一举。 

Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,在这里参数仅仅作为描述。利于区分。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

Symbol('foo') == Symbol('foo') // false

如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后再生成一个 Symbol 值。

const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
sym // Symbol(abc)

Symbol({}) // Symbol([object Object])

Symbol 值不能和其他类型的值进行运算,会报错,但是可以显示的转为字符串。

let sym = Symbol('My symbol');

"your symbol is " + sym
// Uncaught TypeError: Cannot convert a Symbol value to a string

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

Symbol 可以转为布尔值,但是不能转为数值。

Boolean(Symbol())
// true
Number(Symbol())
// Uncaught TypeError: Cannot convert a Symbol value to a number

Symbol.prototype.description

返回 Symbol 的描述。

const sym = Symbol('foo');
sym.description // "foo"

作为属性名的 Symbol

let mySymbol = Symbol();

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

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

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

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

注意,Symbol 作为对象属性名时,不能用点运算符。

const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

因为点运算符后面总是字符串,所以不会读取 mySymbol 作为标识名所指代的那个值,导致 a 的属性名实际上是一个字符串 'mySymbol',而不是一个 Symbol 值。

同理,在对象内部使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。(其实不止是 Symbol 值,其他任何变量名都应该放在 [])

let s = Symbol();
let p = 'parma';
let obj = {
    [s]: 1,
    p: 2,
    [p]: 3
};
console.log(obj);
// {
//     p: 2,
//     parma: 3,
//     [Symbol()]: 1
// }

实例:消除魔术字符串

魔术字符串指的是,在代码中多次出现的,与代码形成强耦合的某一个具体的字符串或者数值。示例

属性名的遍历

Symbol 作为属性名,遍历对象的时候,该属性不会被 for...in...、for...of...循环出来,也不会被 Object.key()、Object.getOwnPropertyNames()、JSON.stringify()返回。

var a = {
    [Symbol()]: 1,
    b: 2
};
console.log(Object.keys(a));
console.log(Object.getOwnPropertyNames(a));
console.log(JSON.stringify(a));

// ["b"]
// ["b"]
// {"b":2}

获取 Symbol 属性名的方法:Object.getOwnPropertySymbol() 方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象所有用作属性名的 Symbol 值。

const obj = {};
const foo = Symbol('foo');

obj[foo] = 'bar';

for (let i in obj) {
  console.log(i); // 无输出
}

Object.getOwnPropertyNames(obj) // []
Object.getOwnPropertySymbols(obj) // [Symbol(foo)]

obj[Object.getOwnPropertySymbols(obj)[0]] // bar

Reflect.ownKeys() 方法可以返回所以类型的键名,包括常规键名和 Symbol 键名。

let obj = {
  [Symbol('my_key')]: 1,
  enum: 2,
  nonEnum: 3
};

Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]

Symbol.for()

接受一个字符串作为参数,然后搜索有没有以该参数作为名称(描述)的 Symbol 值。如果有就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局

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

s1 === s2 // true

Symbol.for() 不会每次调用就返回一个新的 Symbol 值,而是会先检查给定的 key(描述) 是否已经存在,如果不存在才会新建一个值。

Symbol.for() 有登记机制,Symbol() 没有登记机制。

注意:Symbol.for() 为 Symbol 值登记的名字,是全局的,不管有没有在全局环境中运行

Symbol.keyFor()

返回一个已经登记的 Symbol 类型值的 key(描述)。

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

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

内置的 Symbol 值

Symbol.hasInstance

对象的 Symbol.hasInstance 属性,指向一个内部方法。当其他对象使用 instanceof 运算符时,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo 在语言内部时,实际调用的是 Foo[Symbol.hasInstance](foo)。

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true

MyClass 是一个类,new MyClass() 会返回一个实例。该实例的 Symbol.hasInstance 方法,会在 instanceof 运算时自动调用,判断左边的运算实例是否为 Array 的实例。

class Even {
  static [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
  }
}

// 等同于
const Even = {
  [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
  }
};

1 instanceof Even // false
2 instanceof Even // true

1 instanceof Even ==> Even[Symbol.hasInstance](1) ==> Number(1) % 2 === 0  ===> false

Symbol.isConcatSpreadable

Symbol.isConcatSpreadable 属性等于一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可以展开。

Symbol.isConcatSpreadable 的默认值为 undefined,但是对于数组来说,默认是展开的。当然该属性为 true 也是可以展开的。

let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined

let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']

对于对象来说,默认则是不可展开。

let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']

obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']

 

上一篇:JS给数字加千位分隔符


下一篇:安装与配置windbg 的符号文件(转自一片文章的回复,回复者RegKiller)