理解 Es6 中的 Symbol 类型

前言

在 Es6 中引入了一个新的基础数据类型:Symbol,对于其余基本数据类型(数字number,布尔boolean,null,undefined,字符串string)想必都比较熟悉,可是这个Symbol平时用得不多,甚至在实际开发中以为没有什么卵用,可以涉及到的应用场景屈指可数.前端

每每在面试的时候,屡面不爽.下面一块儿来看看的这个数据类型的java

具体解决的问题

在 Es5 的对象属性名中都是字符串,当一对象的属性名出现重复时,后者每每会覆盖前者.web

若使用Symbol就可以保证每一个属性的名字都是独一无二的,至关于生成一个惟一的标识 ID,这样就从根本上防止属性名的冲突面试

Symbol 类型

symbolEs6规范引入的一项新的特性,表示独一无二的值,概括为JS语言的第 7 种数据类型,它是经过Symbol函数生成跨域

经过Symbol()函数来建立生成一个Symbol实例数组

let s1 = Symbol();
console.log(typeof s1); //symbol console.log(Object.prototype.toString.call(s1)); // [object Symbol] 复制代码

在上面示例代码中,用typeof进行了类型的检测,它返回的是Symbol类型,而不是什么string,object之类的浏览器

Es5 中原有的对象的属性名是字符串类型中拓展了一个Symbol类型,也就是说,如今对象的属性名有两种类型微信

  • 字符串类型
  • Symbol 类型

注意markdown

Symbol 函数前不能使用new关键字,不然就会报错,这是由于生成的Symbol是一个原始类型的值,它不是对象app

由于不是对象,因此也不能添加属性,它是一种相似于字符串的数据类型,能够理解为是在字符串类型的一种额外的拓展

Symbol函数能够接收一个字符串作为参数,它是对该Symbol实例的一种描述,主要是为了在控制台显示

Symbol 的描述是可选的,仅用于调试目的或转为字符串时,进行区分,不是访问 symbol 自己

可使用Symbol().description会返回Symbol()的实例描述的具体内容,若是有值,则会返回该描述,若无则会返回undefined

descriptionSymbol的一个静态属性

当使用字符串定义对象的属性名时,若出现同名属性,则会出现属性覆盖问题,而使用Symbol类型定义的对象属性名,则不会,它是独一无二的,每调用一次Symbol()都会生成一个惟一的标识,即便是使用Symbol() 生成的实例描述相同,但它们依旧不相等,总会返回false 以下代码所示

let s1 = Symbol('itclanCoder'); // 定义了一s1变量,它是Symbol()类型,并接收了一个itclanCoder字符串,做为该Symbol的实例
let s2 = Symbol('itclanCoder'); // 实例化了一个s2,Symbol()类型 console.log(s1.description); // itclanCoder console.log(s1.description); // itclanCoder console.log(s1 === s2); // false 复制代码

从第 5 行代码比较结果看出,s1s2是两个不一样的Symbol值,这里让Symbol接受一个参数,若是不加参数,它们在控制台输出的都是Symbol,即便参数相同,可是它们依旧是两个不一样的Symbol

若是您但愿使用拥有同一个Symbol值,那该怎么办?在 Es6 中,提供了一个Symbol.for()方法能够实现,它接受一个字符串做为参数 而后搜索有没有以该参数做为名称的Symbol值

若是有,就返回这个Symbol值,不然就新建一个以该字符串为名称的Symbol值,并会将它注册到全局坏境中

let s1 = Symbol.for('itclanCoder');
let s2 = Symbol.for('itclanCoder'); console.log(s1 === s2); // true 复制代码

在上面的示例代码中,s1s2 都是Symbol实例化出来的值,可是它们都是由Symbol.for方法生成的,指向的是同一个值,地止

  • SymbolSymbol.for 的区别

比较

共同点: 都会生成新的Symbol 不一样点: Symbol.for()会被登记在全局坏境中供搜索,而Symbol()不会,Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,若是不存在才会新建一个Symbol

如:调用Symbol.for('itclanCoder')100 次,每次都会返回同一个Symbol值,可是调用Symbol('itclanCoder')100 次,会返回 100 个不一样的Symbol

Symbol.for("itclanCoder") === Symbol.for("itclanCoder") // true
 Symbol("itclanCoder") === Symbol("itclanCoder") // false 复制代码

在上面代码中,因为Symbol()写法没有登记机制,因此每次调用都会返回一个不一样的值,也就是每次都会在栈内存中从新开辟一块空间

也能够经过Symbol.keyFor()方法返回一个已登记的Symbol类型值的key,经过该方法检测是否有没有全局注册

let s1 = Symbol.for("itclan");
console.log(Symbol.keyFor(s1)) // "itclan"  let s2 = Symbol("itclan"); console.log(Symbol.keyFor(s2)) // undefined  复制代码

在上面的代码中,变量s2属于未被登记的Symbol值,因此就返回undefined

注意

Symbol.for()是为Symbol值登记的名字,在整个全局做用域范围内都起做用

function foo() {
 return Symbol.for('itclan'); }  const x = foo(); const y = Symbol.for('itclan'); console.log(x === y); // true 复制代码

在上面代码中,Symbol.for('itclan')是在函数内部运行的,可是生成的 Symbol 值是登记在全局环境的。因此,第二次运行Symbol.for('itclan')能够取到这个 Symbol 值

  • 应用场景:Symbol.for()

这个全局记录特性,能够用在不一样的iframeservice worker中取到同一个值

在前端开发中,有时候会用到iframe,可是iframe之间相互隔离的,有时候想要取到不一样的iframe中同一份数据,那么这个Symbol.for()就派上用场了的

以下示例代码所示

let iframe = document.createElement('iframe');
iframe.src = String(window.location); document.body.appendChild(iframe);  iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') // true 复制代码

在上面代码中,iframe窗口生成的Symbol 值,能够在主页面拿获得,在整个全局做用域内均可以取到

Symbol 应用场景

  • 应用场景 1-使用Symbol来做为对象属性名(key)

在 Es6 以前,一般定义或访问对象的属性都是使用字符串,以下代码所示

let web = {
 site: "http://itclan.cn",  name: "itclanCoder" } console.log(web['site']); // http://itclan.cn console.log(web['name']); // itclanCoder 复制代码

访问变量对象的属性,除了能够经过对象.属性名的方式外,能够经过对象['属性名']的方式进行访问,若是一个对象中出现了同名属性那么后者会覆盖前者

因为每调用一次Symbol函数,生成的值都是不相等的,这意味着Symbol值能够做为标识符,用于对象的属性名,就能保证不会出现同名的属性

针对一个对象由多个模块构成的状况就变得很是有用了的,使用Symbol能放置某一个键被不当心改写或覆盖

Symbol能够用于对象属性的定义和访问

以下示例代码所示

const PERSON_NAME = Symbol();
const PERSON_AGE = Symbol();  let person = {  [PERSON_NAME]: "随笔川迹" }  person[PERSON_AGE] = 20;  console.log(person[PERSON_NAME]) // 随笔川迹 console.log(person[PERSON_AGE]) // 20 复制代码

在上面的示例代码中,使用Symbol建立了PERSON_NAME,PERSON_AGE两个Symbol类型,可是在实际开发中却带来了一些问题

当您使用了Symbol做为对象的属性key后,你若想对该对象进行遍历,因而用到了Object.keys(),for..in,for..of,Object.getOwnPropertyNames()、JSON.stringify()进行枚举对象的属性名

你会发现使用Symbol后会带来一个很是使人难以接受的现实,以下示例代码所示

let person = {
 [Symbol('name')]: '随笔川迹',  age: 20,  job: 'Engineer' } console.log(Object.keys(person)) // ["age", "job"] for(var i in person) {  console.log(i); // age job }  Object.getOwnPropertyNames(person) // ["age", "job"] JSON.stringify(person); // "{"age":20,"job":"Engineer"}" 复制代码

经过上面的示例代码结果可知,Symbol类型实例化出的key是不能经过Object.keys(),for..in,for..of,来枚举的

它也没有包含子自身属性集合Object.getOwnPropertyName()当中,该方法没法获取到

利用该特性,咱们能够把一些不须要对外操做和访问的属性使用Symbol来定义

这样,咱们在定义接口的数据对象时,能够决定对象的哪些属性,对内私有操做与对外公有操做变得可控,更加的方便

使用常规的方法,没法获取到以Symbol方式定义对象的属性,在 Es6 中,提供了一个专门针对Symbol的 API

Object.getOwnPropertySymbols()方法,能够获取指定对象的全部Symbol属性名,该方法会返回一个数组

它的成员是当前对象的全部用做属性名的 Symbol 值

let person = {
 [Symbol('name')]: '随笔川迹',  age: 20,  job: 'Engineer' }  // 使用Object的API Object.getOwnPropertySymbols(person) // [Symbol(name)]  复制代码

以下是Object.getOwnPropertySymbols()方法与for..in循环,Object.getOwnPropertyNames方法进行对比的例子

const person = {};
const name = Symbol('name');  person[name] = "随笔川迹"  for(let i in person) {  console.log(i); // 无任何输出 }  Object.getOwnPropertyNames(person); // [] Object.getOwnPropertySymbols(person); // [Symbol('name')] 复制代码

在上面代码中,使用for...in循环和Object.getOwnPropertyNames()方法都得不到 Symbol 键名,须要使用Object.getOwnPropertySymbols()方法。

若是想要获取所有的属性,可使用一个新的 API,Reflect.ownKeys()方法能够返回全部类型的键名,包括常规键名和 Symbol 键名

let person = {
 [Symbol('name')]: "川川",  enum: 2,  nonEnum: 3 };  Reflect.ownKeys(person) // ["enum", "nonEnum", Symbol(name)] 复制代码

正因为以Symbol 值做为键名,不会被常规方法(for..in,for..of)遍历获得。咱们能够利用这个特性,为对象定义一些非私有的、但又但愿只用于内部的方法,达到保护私有属性的目的

  • 应用场景 2:使用 Symbol 定义类的私有属性/方法

JavaScript 是一弱类型语言,弱并非指这个语言功能弱,而所指的是,它的类型没有强制性,是没有如java等面向对象语言的访问控制关键字private的,类上全部定义的属性和方法都是公开访问的,固然在TypeScript中新增了一些关键字,解决了此问题的

有时候,类上定义的属性和方法都能公开访问,会形成一些困扰

而有了Symbol类的私有属性和方法成为了实现

以下示例代码

let size = Symbol('size');  // 声明定义了一个size变量,类型是Symbol(),类型描述内容是size
 class Collection { // class关键字定义了一个Collection类  constructor() { // 构造器`constructor`函数  this[size] = 0; // 在当前类上私有化了一个size属性  }   add(item) { // Collection类下的一个方法  this[this[size]] = item;  this[size]++;  }   static sizeOf(instance) { // 静态属性  return instance[size];  } }  let x = new Collection(); // 实例化x对象 Collection.sizeOf(x) // 0  x.add('foo'); // 调用方法 Collection.sizeOf(x) // 1  Object.keys(x) // ['0'] Object.getOwnPropertyNames(x) // ['0'] Object.getOwnPropertySymbols(x) // [Symbol(size)] 复制代码

上面代码中,对象 xsize 属性是一个 Symbol 值,因此 Object.keys(x)Object.getOwnPropertyNames(x)都没法获取它。这就形成了一种非私有的内部方法的效果

  • 应用场景 3-模块化机制

结合Symbol与模块化机制,类的私有属性和方法完美实现,以下代码所示 在文件a.js

const PASSWORD = Symbol();  // 定义一个PASSWORD变量,类型是Symbol
 class Login() { // class关键字声明一个Login类  constructor(username, password) { // 构造器函数内初始化属性  this.username = username;  this[PASSWORD] = password;  }   checkPassword(pwd) {  return this[PASSWORD] === pwd;  }  } export default Login; 复制代码

在文件b.js

import Login from './a'
 const login = new Login('itclanCoder', '123456'); // 实例化一个login对象  login.checkPassword('123456'); // true login.PASSWORD; // 访问不到 login[PASSWORD]; // 访问不到 login['PASSWORD'] // 访问不到  复制代码

由于经过Symbol定义的PASSWORD常量定义在a.js模块中,外面的模块是获取不到这个Symbol的,在外部没法引用这个值,也没法改写,也不可能在在建立一个如出一辙的Symbol出来

由于Symbol是惟一的

a.js模块中,这个PASSWORDSymbol类型只能在当前模块文件(a.js)中内部使用,因此使用它来定义的类属性是没有办法被模块外访问到的

这样就达到了一个私有化的效果

  • 应用场景 4-使用Symbol来替代常量

在使用React中,结合Redux作公共数据状态管理时,当想要改变组件中的某个状态时,reducer是一个纯函数,它会返回一个最新的状态给store,返回的结果是由actionstate共同决定的

action是一个对象,有具体的类型type值,若是你写过几行Redux的代码,就会经常看到,进行action的拆分,将事件动做的类型定义成常量

const CHANGE_INPUT_VALUE = 'CHANGE_INPUT_VALUE';  // 监听input框输入值的常量
const ADD_INPUT_CONTENT = 'ADD_INPUT_CONTENT'; // 添加列表 const DELETE_LIST = 'DELETE_LIST'; // 删除列表  function reducer(state, action) {  const newState = JSON.parse(JSON.stringify(state));  switch(action.type) {  case CHANGE_INPUT_VALUE:  // ...  case ADD_INPUT_CONTENT:  // ...  case DELETE_LIST;  // ...  default:  return state;  }  } 复制代码

以上代码在Redux中很常见,将action对象中的type值,给抽离出来,定义一个常量存储,来表明一种业务逻辑,一般但愿这些常量是惟一的,在Redux中定义成常量,是为了便于调试查错

经常由于取type值时,很是苦恼.

如今有了Symbol,改写一下,就能够这样

const CHANGE_INPUT_VALUE = Symbol()
const ADD_INPUT_CONTENT = Symbol(); const DELETE_LIST = Symbol()  function reducer(state, action) {  const newState = JSON.parse(JSON.stringify(state));  switch(action.type) {  case CHANGE_INPUT_VALUE:  // ...  case ADD_INPUT_CONTENT:  // ...  case DELETE_LIST;  // ...  default:  return state;  }  } 复制代码

经过Symbol定义字符串常量,就保证了三个常量的值惟一性

划重点

  • 常量使用Symbol值最大的好处,就是其余任何值都不可能有相同的值了,能够保证常量的惟一性,所以,能够保证上面的switch语句按照你设计的方式条件去工做

  • Symbol值做为属性名时,该属性是公开属性,不是私有属性

  • 应用场景 5-注册和获取全局的`Symbol

在浏览器窗口(window)中,使用Symbol()函数来定义生成的Symbol实例是惟一的

可是若应用涉及到多个window,最多见的就是在各个页面窗口中嵌入iframe了,并在各个iframe页面中取到来自同一份公共的数据源

也就是在各个window中使用的某些Symbol但愿是同一个,那么这个时候,使用Symbol()就不行不通了

由于用它在不一样window中建立的Symbol实例老是惟一的,而咱们须要的是在全部这些window环境下保持一个共享的Symbol值。

在这种状况下,咱们就须要使用另外一个 API 来建立或获取Symbol,那就是Symbol.for(),它能够注册或获取一个window间全局的Symbol实例,它是Symbol的一个静态方法

这个在前面已经提到过一次,这个仍是有那么一点点用处,因此在提一嘴的

以下示例代码所示

let gs1 = Symbol.for('global_symbol_1')  //注册一个全局Symbol
let gs2 = Symbol.for('global_symbol_1') //获取全局Symbol  console.log(gs1 === gs2 ) // true 复制代码

通过Symbol.for()实例化出来的Symbol字符串类型,只要描述的内容相同,那么不光是在当前window中是惟一的,在其余全局范围内的window也是惟一的,而且相同

该特性,如果建立跨文件可用的symbol,甚至跨域(每一个window都有它本身的全局做用域) , 可使用 Symbol.for()取到相同的值

也就是说,使用了Symbol.for()在全局范围内,Symbol类型值能够共享

注意事项

  • Symbol 值不能与其余类型的值进行运算-会报错
let symItclan = Symbol('itclan');
 console.log("主站" + symItclan) console.log(`主站 ${symItclan}`) // Uncaught TypeError: Cannot convert a Symbol value to a string 复制代码
  • Symbol能够显示转为字符串
let SyItclanCoder = Symbol('https://coder.itclan.cn');
 console.log(String(SyItclanCoder)) // Symbol(https://coder.itclan.cn) console.log(SyItclanCoder.toString()) // Symbol(https://coder.itclan.cn) 复制代码
  • Symbol值能够转为布尔值,可是不能转为数值
let sym = Symbol();
console.log(Boolean(sym)) // true console.log(!sym) // false  if (sym) {  // ... }  Number(sym) // TypeError Cannot convert a Symbol value to a number sym + 2 // TypeError 复制代码

由上面的错误提示能够看出,Symbol不能转换为数字,没法作相应的运算

  • Symbol函数不能使用new命令

Symbol函数前不能使用new命令,不然就会报错,Symbol是一个原始类型的值,不是对象,它是相似字符串的数据类型

  • Symbol值做为对象属性名时,不能用点运算符

Symbol值做为对象的属性名时,访问它时,不能用点运算符

const username = Symbol();
const person = {};
person.username = '随笔川迹';
person[username]; // undefined
person['username']; // 随笔川迹
复制代码

第 4 行代码值为undefined,由于点运算符后面老是字符串,因此不会读取username做为标识符名所指代的那个值

致使person对象的属性名其实是一个字符串,而不是一个Symbol

因而可知:在对象内部,使用Symbol类型定义属性名时,Symbol值必须放在中括号之中

let s = Symbol();
let obj = {  [s]: function(arg) {  return arg;  } }  obj[s]("itclanCoder") 复制代码

在上面的代码中,若是变量s不放在中括号中,该属性的键名就是字符串s,而不是定义Symbol类型值

总结

本文主要介绍了Es6Symbol的常见使用,Symbol是一种新的基础类型,它形式字符串的数据类型,是字符串类型的一种额外拓展

经常使用于做为对象属性的键名,每一个从Symbol()返回的symbol值都是惟一的,可保证对象的每一个属性名的惟一性,可解决属性名的冲突问题

Symbol()函数会返回symbol类型的值,该类型具备静态属性(如Symbol().description,)和静态方法(Symbol.for(),Symbol.keyFor())

固然也介绍了Symbol的一些常见应用场景,做为对象的属性名(key),定义类的私有属性和方法,替代常量,以及注册全局Symbol等,以及一些注意事项

关于Symbol暂且就这么多,仍是得多多使用

更多内容,您可关注微信itclanCoder公众号,一个只传递和分享给你带来启发智慧有用的号

原文出处-https://coder.itclan.cn/-理解Es6中的Symbol类型

相关文章
相关标签/搜索