在 Es6 中引入了一个新的基础数据类型:Symbol
,对于其余基本数据类型(数字number
,布尔boolean
,null
,undefined
,字符串string
)想必都比较熟悉,可是这个Symbol
平时用得不多,甚至在实际开发中以为没有什么卵用,可以涉及到的应用场景屈指可数.前端
每每在面试的时候,屡面不爽.下面一块儿来看看的这个数据类型的java
在 Es5 的对象属性名中都是字符串,当一对象的属性名出现重复时,后者每每会覆盖前者.web
若使用Symbol
就可以保证每一个属性的名字都是独一无二的,至关于生成一个惟一的标识 ID,这样就从根本上防止属性名的冲突面试
symbol
是Es6
规范引入的一项新的特性,表示独一无二的值,概括为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
description
是Symbol
的一个静态属性
当使用字符串定义对象的属性名时,若出现同名属性,则会出现属性覆盖问题,而使用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 行代码比较结果看出,s1
与s2
是两个不一样的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 复制代码
在上面的示例代码中,s1
和 s2
都是Symbol
实例化出来的值,可是它们都是由Symbol.for
方法生成的,指向的是同一个值,地止
Symbol
与 Symbol.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()
这个全局记录特性,能够用在不一样的iframe
火service 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
来做为对象属性名(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
)遍历获得。咱们能够利用这个特性,为对象定义一些非私有的、但又但愿只用于内部的方法,达到保护私有属性的目的
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)] 复制代码
上面代码中,对象 x
的 size
属性是一个 Symbol
值,因此 Object.keys(x)
、Object.getOwnPropertyNames(x)
都没法获取它。这就形成了一种非私有的内部方法的效果
结合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
模块中,这个PASSWORD
的Symbol
类型只能在当前模块文件(a.js
)中内部使用,因此使用它来定义的类属性是没有办法被模块外访问到的
这样就达到了一个私有化的效果
Symbol
来替代常量在使用React
中,结合Redux
作公共数据状态管理时,当想要改变组件中的某个状态时,reducer
是一个纯函数,它会返回一个最新的状态给store
,返回的结果是由action
和state
共同决定的
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
值做为属性名时,该属性是公开属性,不是私有属性
在浏览器窗口(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
类型值
本文主要介绍了Es6
中Symbol
的常见使用,Symbol
是一种新的基础类型,它形式字符串的数据类型,是字符串类型的一种额外拓展
经常使用于做为对象属性的键名,每一个从Symbol()
返回的symbol值
都是惟一的,可保证对象的每一个属性名的惟一性,可解决属性名的冲突问题
Symbol()
函数会返回symbol
类型的值,该类型具备静态属性(如Symbol().description
,)和静态方法(Symbol.for()
,Symbol.keyFor()
)
固然也介绍了Symbol
的一些常见应用场景,做为对象的属性名(key),定义类的私有属性和方法,替代常量,以及注册全局Symbol
等,以及一些注意事项
关于Symbol
暂且就这么多,仍是得多多使用
更多内容,您可关注微信itclanCoder公众号,一个只传递和分享给你带来启发智慧有用的号