Symbol 的出现给我们带来什么改变

1,151 阅读5分钟

大家可能在实际开发过程中很少会用到 Symbol,既然 ES6 引入了 Symbol 新特性,应该是为了解决问题而引入的。那么 Symbol 可以解决什么样问题,以及应该如何合理地使用 Symbol,带着这些问题我们今天来说一说 Symbol 这个 ES6 新特性。

001.png

Symbol 是基本数据类型

symbol 是在 ES6 中提出新特性,symbol 和数值型、字符串、布尔型一样也是 javascript 的基本数据类型。

const num1 = Number(1)
console.log(num1)

const num2 = 1;

console.log(num1 === num2);

我们很少用 Number(1) 这种方式创建数值型的 1,然后赋值给变量 num1, 通常我们更喜欢1 这种简便方式定义基本类型数据,而对于同样是基本数据类型 Symbol 我们只能用 Symbol() 这种方式来创建一个 symbol 类型。这样一来你也就是理解了为什么Symbol 没有构造函数。

如何创建 Symbol 类型数值

提供 2 种方法来创建 Symbol 值 ,分别是 Symbol() 函数 和 Symbol.for() 方法,下面通过代码给大家说明如何创建 Symbol。

Symbol() 函数

const symb1 = Symbol();
console.log(symb1)

输出如下

Symbol()

在给大家解释了为什么不用 new 来创建 Symbol 类型的数值,因为 Symbol 不是对象,只是是一个基本数据类型。

const symb1 = new Symbol();
TypeError: Symbol is not a constructor

每个从Symbol()返回的 symbol 值都是唯一的,使用 Symbol() 创建新的symbol值,并用一个可选的字符串作为其描述,这描述相同的两个 Symbol 值依然是不同的。

const symb1 = Symbol("symbol description");
console.log(symb1)
const symb1 = Symbol("machine learning");
const symb2 = Symbol("machine learning");

console.log(symb1 == symb2) //false

通过上面代码可以看出我们定义 2 个具有完全相同描述的 Symbol 结果这 2 个 Symbol 是不同的。

值得注意的是,当我们要用 Symbol 类型数值来做对象属性名称,需要对这个 Symbol 类型的数值加上一对方括号才能生效。

const symb1 = Symbol("symbol description");
// console.log(symb1)

let tut = {
    name:"basic machine learning",
    [symb1]:new Date()
}

for (prop in tut){
    console.log(prop)
    console.log(tut[prop])
}
name
basic machine learning

如果 symbol 类型的值在属性赋值语句中被用作标识符,该属性是匿名的, 并且是不可枚举的 ,所以用prop in tut 方法遍历对象 tut 所有属性时,发现 Symbol 类型数值定义属性没有边显示遍历出来,这主要是 因为这个属性是不可枚举的,不会在循环结构 for( ... in ...) 中作为出现,同样在 Object.getOwnPropertyNames() 的返回数组里也是无法找到用 Symbol 类型值得属性标识符。

console.log(Object.getOwnPropertyNames(tut))
[ 'id', 'name', 'author' ]

通过这个 Object.getOwnPropertySymbols() 会获取对象中用 Symbol 类型数值作为标识的属性。

console.log(Object.getOwnPropertySymbols(tut))//[ Symbol(id) ]

Symbol.for() 方法

一种方式创建,就是用Symbol.for()

const symb1 = Symbol.for("create time");

全局共享的 Symbol

Symbol 类型的数值也不一定是唯一的,例如下面这种情况,其实这些全局 base Symbol 。当我们用 Symbol.for 创建一个 Symbol 类型的数据,并且给定一个字符串,这里字符串不再是说明文字,而是在全局 Symbol 创建了一个索引对应一个 Symbol 数值,所以相同索引会指向同一个 Symbol 类型数值,下面代码证明在全局 Symbol 中通过搜索同一个索引得到两个 Symbol 类型数值是相同的。

const symb1 = Symbol.for("create time");
const symb2 = Symbol.for("create time");

console.log(symb1 === symb2);//true
console.log(Symbol.keyFor(symb1));//create time
const symb1 = Symbol.for("create time");
const symb2 = Symbol.for("create time");
const tut1 = {
    name:"basic pyTorch",
    [symb1]: new Date()
}

console.log(tut1[symb2])

Symbol 的应用场景

接下来看一看 Symbol 在实际开发中应该如何使用 Symbol 解决一些覆盖或 id 冲突类似的问题。

扩展对象属性

到现在为止,我想大家大概已经了解到了 Symbol 的概念,以及如何创建和使用 Symbol。那么接下里才是重点,也就是我们在哪些场合下如何去使用 Symbol 解决问题。避免覆盖原有的属性,所以我们可以对于复杂对象追加属性时候可以用使用 Symbol 类型数值作为属性名,这样可以很好避免预防属性覆盖。

const tut = {
    id:123,
    name:"basic machine learning",
    author:"matthew"
};


tut.id = 789;
console.log(tut.id);//789
const tut = {
    id:123,
    name:"basic machine learning",
    author:"matthew"
};

const idSymbol = Symbol("id")

tut[idSymbol] = 789;
console.log(tut.id);\\123

可以作为静态变量使用

下面例子很好说明了用 Symbol 类型数据唯一性可以用于作为静态变量,下面第一段代码中问题就是当 banana = "yellow也会走到分支 BLUE,这是因为变量 BLUE 作为字符串类型数值并不具有唯一性,在这种场景我们可以用 Symbol 的唯一性。

const RED = 'red';
const YELLOW = 'yellow';
const BLUE = 'blue';

function getLogLevel(color){
    switch(color){
        case RED:
            return 'error';
        case YELLOW:
            return 'warning';
        case BLUE:
            return 'info';
        default:
            console.log("unkown level ")
    }
}


const banana = "yellow";

console.log(getLogLevel(banana))// warning
class Sequence{
    constructor(){
        this.length = 0
    }
    add(layername,layer){
        this[layername] = layer;
        this.length++
    }
}


let model = new Sequence();
model.add("input","liner");
model.add("hidden layer","dense layer")
model.add("output","softmax")

for (layer in model){
    console.log(model[layer])
}

002.jpeg

避免覆盖,便于团队开发

class AlertService{
    constructor(){
        this.alerts = {}
    }

    addAlert(symbol, alertText){
        this.alerts[symbol] = alertText
        this.renderAlert();
    }

    removeAlert(symbol){
        delete this.alerts[symbol]
    }

    renderAlert(){

    }
}

const alertService = new AlertService()

class MyComponent{
    constructor(thing){
        this.componentId = Symbol(thing)
    }

    errorHandler(msg){
        alertService.addAlert(this.componentId,msg)
    }
}

let userList = MyComponent("listComponent")
let itemList = MyComponent("listComponent")

let registerForm = MyComponent("inputComponent");

userList.errorHandler("some text 1");
itemList.errorHandler("some text 2")

上面代码我们可以定义相同名称的组件都给相同名字listComponent,但是this.componentId = Symbol(thing) 因为使用 Symbol 类型数值作为组件从而避免 Id 冲突,好处不言而喻,对于多人开发时,很好避免冲突,估计到现在大家已经对什么是 Symbol 以及如何使用 Symbol 有一定认识了。