js ES6 中新增的 Symbol 原始数据类型

目录

前言

一、创建一个 Symbol 类型的值——Symbol() 函数

二、Symbol 类型与其他原始数据类型的关系

1、将 Symbol 转为字符串

2、将 Symbol 转为布尔值

三、Symbol 值作为属性名

1、Symbol 值作为属性名,读写该属性时必须使用方括号

2、在包含 Symbol 类型的属性名的对象中,遍历出包含全部 Symbol 类型的属性。

(1)、Object.getOwnPropertySymbols() 方法遍历对象

(2)、Reflect.ownKeys() 方法遍历对象

3、使用 Symbol 为对象定义一些非私有的且只用于内部的方法(隐藏需要保护的对象的属性)

四、定义 Symbol 类型的常量

五、Symbol 实例的属性

六、Symbol 的方法

1、Symbol.for() 方法

2、Symbol.keyFor()方法

七、内置的 Symbol 属性

八、Symbol 的使用场景

1、用 Symbol 作为常量

2、用 Symbol 作为对象属性名

3、用 Symbol 定义类的私有属性和方法


前言

ES6 的原始数据类型除了 Number 、String 、Boolean 、Object、null 和 undefined 外,还新增了 Symbol 类型 。

Symbol 原始数据类型表示独一无二的值,用来定义对象的唯一属性名。

 

一、创建一个 Symbol 类型的值——Symbol() 函数

Symbol() 函数用来创建一个 Symbol 类型的值。该函数可以接受一个参数:(可选的)一个字符串。该参数表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。不过,相同参数的 Symbol 函数的返回值并不相等

let s = Symbol();
console.log(sy);// Symbol()
typeof s;// "symbol"

let s1 = Symbol('foo');
let s2 = Symbol('bar');
console.log(s1); // Symbol(foo)
console.log(s2); // Symbol(bar)

let sy1 = Symbol("kk"); 
let sy2 = Symbol("kk"); 
sy1 === sy2;// false

如果 Symbol 的参数是一个对象,当且仅当手动给该对象定义了 toString() 方法,Symbol 就会默认调用这个方法——将其转为字符串,然后才生成一个 Symbol 类型的值。

let obj = {
    a: "asd",
    toString: function() {
        return this.a;
    },
    abc: function(){
        console.log("========");
    }
};

// 非 Symbol 中使用该对象,两个自定义方法都不会自动执行
console.log(obj);// {a: "asd", toString: ƒ, abc: ƒ}

// Symbol 函数中使用该对象,自定义的 toString() 方法自动执行了
let sym = Symbol(obj);
console.log(sym);// Symbol(asd)

 

二、Symbol 类型与其他原始数据类型的关系

  • Symbol 是原始数据类型,所以,它不能使用 new 命令,也不能为其添加自定义的属性,否则报错。
  • Symbol 值不能直接与其他类型的值进行运算,可以转为字符串(“显式”转换)或 布尔值(“显式 / 隐式”转换)后参与运算。

1、将 Symbol 转为字符串

let sym1 = Symbol('world');
typeof sym1;// symbol
sym1;// Symbol(world)

// 将 Symbol “显示”转换成 字符串 的两种方式
let m = String(sym1);
let n = sym1.toString();
typeof m;// string
typeof n;// string
m;// 'Symbol(world)'
n;// 'Symbol(world)'

// 将 Symbol “隐式”转为字符串会报错
sym1 + " ";// TypeError: Cannot convert a Symbol value to a string

2、将 Symbol 转为布尔值

let sym2 = Symbol();
typeof sym2;// symbol
sym2;// Symbol()

// 隐式
!sym2;// false

// 显示
let boo = Boolean(sym2);
typeof boo;// boolean
boo;// true

 

三、Symbol 值作为属性名

  • Symbol 值作为对象属性名,读写该属性时必须使用方括号。
  • Symbol 值作为属性名时,该属性是公开的,但不会在全局注册。
  • Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。

1、Symbol 值作为属性名,读写该属性时必须使用方括号

由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

let mySymbol = Symbol();

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

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

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

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

2、在包含 Symbol 类型的属性名的对象中,遍历出包含全部 Symbol 类型的属性。

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

(1)、Object.getOwnPropertySymbols() 方法遍历对象

Symbol 提供了一个 Object.getOwnPropertySymbols() 方法,用于获取指定对象的所有 Symbol 类型的属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

const objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]

(2)、Reflect.ownKeys() 方法遍历对象

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

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

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

3、使用 Symbol 为对象定义一些非私有的且只用于内部的方法(隐藏需要保护的对象的属性)

由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。

let size = Symbol('size');

class Collection {
  constructor() {
    this[size] = 0;
  }

  add(item) {
    this[this[size]] = item;
    this[size]++;
  }

  static sizeOf(instance) {
    return instance[size];
  }
}

let x = new Collection();
Collection.sizeOf(x) // 0

x.add('foo');
Collection.sizeOf(x) // 1

Object.keys(x) // ['0']
Object.getOwnPropertyNames(x) // ['0']
Object.getOwnPropertySymbols(x) // [Symbol(size)]

 

四、定义 Symbol 类型的常量

const COLOR_RED    = Symbol();
const COLOR_GREEN  = Symbol();

function getComplement(color) {
  switch (color) {
    case COLOR_RED:
      return COLOR_GREEN;
    case COLOR_GREEN:
      return COLOR_RED;
    default:
      throw new Error('Undefined color');
    }
}

常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的 switch 语句会按设计的方式工作。 

 

五、Symbol 实例的属性

Symbol 的实例都具有一个属性 description,该属性用来直接返回 Symbol 的描述。

const sym = Symbol('foo');

sym.description // "foo"

 

六、Symbol 的方法

1、Symbol.for() 方法

该方法用来重新获取并使用同一个 Symbol 值。

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

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

s1 === s2 // true

上面代码中,s1和s2都是 Symbol 值,但是它们都是由同样参数的Symbol.for方法生成的,实际上是同一个值。

由此可知:Symbol.for() 与 Symbol() 这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

Symbol.for("bar") === Symbol.for("bar")
// true

Symbol("bar") === Symbol("bar")
// false

2、Symbol.keyFor()方法

该方法和 Symbol.for() 方法一起使用,返回一个已登记的 Symbol 类型值的key。

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

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

 

七、内置的 Symbol 属性

ES6 为 Symbol 提供了 11 个内置的属性,指向语言内部使用的方法。

  • hasInstance:布尔值,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。
  • isConcatSpreadable:布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
  • species:指向一个构造函数。创建衍生对象时,会使用该属性。
  • match:指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。
  • replace:指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
  • search:指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
  • split:指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
  • iterator:指向该对象的默认遍历器方法。
  • toPrimitive:指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
  • toStringTag:指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。
  • unscopables:指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。(不过,ES6 严格模式下,不让使用 with 语句)。

详细使用案例,请戳此链接:https://es6.ruanyifeng.com/#docs/symbol 里的第 8 个标题“内置的 Symbol 值”。

 

八、Symbol 的使用场景

1、用 Symbol 作为常量

我们经常定义一组常量来代表一种业务逻辑下的几个不同类型,我们通常希望这几个常量之间是唯一的关系,为了保证这一点,我们需要为常量赋一个唯一的值。

一般定义使用常量:

const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'

使用 Symbol 定义使用常量:

const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()

2、用 Symbol 作为对象属性名

定义一个对象时,建议使用 Symbol 来定义那些不需要对外操作和访问的属性,减少重名覆盖。

  • 在 for...in、for...of、Object.keys()、Object.getOwnPropertyNames() 和 JSON.stringify() 中都获取不到对象的 symbol 类型的属性名。
  • Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 都可以获取到对象的 Symbol 类型的属性名。

通常定义或访问对象的属性:

let obj = {
  number: 123,
  "str": "hello world"
}

obj["number"] // 123
obj["str"] // 'hello world'

使用 Symbol 定义和访问对象的属性:

const PROP_NAME = Symbol();
const PROP_AGE = Symbol();

let obj = {
  [PROP_NAME]: "Mike",
}
obj[PROP_AGE] = 16;

obj[PROP_NAME] // 'Mike'
obj[PROP_AGE] // 16

3、用 Symbol 定义类的私有属性和方法

JS中没有访问控制关键字。类上所有定义的属性或者方法都是可以公开的。使用Symbol,再加上模块化机制以后,类的私有属性和方法才能变成可能。

例如:

// login/index.js
const PASSWORD = Symbol();

class Login {
  constructor(username, password) {
    this.username = username;
    this[PASSWORD] = password;
  }

  checkPassword(pwd) {
      return this[PASSWORD] === pwd;
  }
}

export default Login;
import Login from './login'

const login = new Login('admin', '123456')
login.checkPassword('123456');// true

login.PASSWORD;// 报错
login[PASSWORD];// 报错
login["PASSWORD"];// 报错

上述代码中,由于 Symbol 常量 PASSWORD 被定义在 login/index.js 所在的模块中,外面的模块获取不到这个 Symbol,也不可能再创建一个一模一样的 Symbol 出来(因为Symbol是唯一的)。因此这个 PASSWORD 的 Symbol 只能被限制在其所在模块的内部使用,从而实现了私有化的目的。

【注意】

注意区分:Symbol 可以用来定义类的私有属性和方法,ES7 的 static 可以用来定义静态属性和方法。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值