Symbol 对象是什么?
Symbol 对象称为符号对象,是 es2015(也就是 es6)中新添加的数据类型。通过 Symbol() 可以得到一个唯一的值,所以这个值很适合做标识符。
概念
Symbol() function 返回一个类型为 symbol 的值,Symbol 有静态属性,原型上也有 methods,但是缺少 constructor,所以你不能 new Symbol()
来执行;
引用官方翻译,符号是一种特殊的、不可变的数据类型,它可以作为对象属性的标识符使用。符号对象是一个对的符号原始数据类型的隐式对象包装器。
类型
let sym = Symbol(); // Symbol中参数为字符串,用来描述。 console.log(typeof sym); //"symbol"
const sym1 = Symbol('abc'); const sym2 = Symbol('cba'); console.log(sym1,sym2); //Symbol(abc) Symbol(cba) //参数的作用就是描述,便于调试 console.log(sym1.toString()) //'Symbol(abc)'
唯一性
Symbol('foo') === Symbol('foo'); // false
new 关键字
const sym = new Symbol(); // TypeError
获取 symbol 类型值的三种方式
通过 Symbol 对象
也就是前面说的 let sym = Symbol();
得到一个为一个的 symbol 值
Symbol.for(string)
可以注册一个 symbol,再次调用 Symbol.for(string) 会得到这个 symbol 值,区别于 Symbol() 是唯一的
let sym = Symbol('abc'); const sym1 = Symbol.for('abc'); console.log(sym === sym1); //false console.log(sym1 === Symbol.for('abc')); //true
Symbol.iterator
这个比较特别,得到的也是 symbol 类型的值,是es6中提出用到对象中,被for...of遍历所用,下面将进行详细介绍。
由于这个值是标准规范提出,用于每个 object 中,所以应该是固定的值。
console.log(Symbol.iterator === Symbol.iterator); //true
用途
在我看来,symbol 更多是应用于es6规范中,由于它的值唯一的特性,可以解决变量名,属性名冲突的问题,并切 Symbol 提出了一些属性和方法,用于过渡以及实现一些特殊的用途,比如对象的迭代器,instanceof 的拓展等等。
栗子
像我们项目中大量的使用常量定义字符串,尤其是 react+redux 的项目中,我觉得用 Symbol 就不错。
const GETLIST_SUCCESS = Symbol('get-list-success'); const getList = () => dispatch({ type: GETLIST_SUCCESS } ) const list = function(state = {}, action) { switch(action.type): case GETLIST_SUCCESS: // code }
我这里只是举一个例子,这种标识性的 Symbol 可以用的地方。大家可以不必关注 redux。
另外,Symbol 值可以做为对象的 key 值,这样就能保证不会出现同名的属性名,防止对象属性被覆盖。下面我们将介绍 Symbol 作为属性名的写法。
作为对象的 key
对象 [] 方括号的形式
const obj = {} const sym = Symbol(); obj[sym] = 'syj';
对象内部定义
const sym = Symbol(); const obj = { [sym]: 'syj' }
通过 Object.defineProperty 定义
const sym = Symbol(); const obj = Object.defineProperty({}, sym, { enumerable: true, //可枚举 writable: true, //可赋值运算符改变 configurable: true, //可改变,可删除 value: 'syj' } )
注意,symbol 作为属性名不能通过 .
的形式添加。
通过上述三种方式,我们就可以向对象添加key不会重复的symbol值了。意味着我们可以创建非字符串类型的属性名称,以防止使用常规手段来探查这些名称。
symbol 类型的key的遍历
当我们用 symbol 设置了对象的key以后,他是不会被之前的 for...in
,Object.keys()
遍历出来的,需要用 Object.getOwnPropertySymbols()
获取,得到一个所有这个对象中的 symbol 属性名的数组。
const sym1 = Symbol('1'); const sym2 = Symbol('2'); const obj = { [sym1]: 'syj', [sym2]: 'fy' } const ary = Object.getOwnPropertySymbols(obj); console.log(ary); //[ Symbol(1), Symbol(2) ]
内置的 Symbol 值
JavaScript 内建的一些在 ECMAScript 5 之前没有暴露给开发者的符号,它们代表语言的内部行为。
Symbol.iterator 属性
我觉得是最重要的属性,它的提出使对象可以使用迭代器遍历,之前只有 Array、String 等,这种内置 Symbol.iterator 属性的可以使用迭代器。ECMAScript 旨在使 JS 中使用一种方法,比如for...of
就可以遍历序列数据,不需要关注它内部的数据结构。其实就是 JS 往 C、JAVA 语言靠拢的趋势吧。
为了避免此部分过于重,我们后面会专门研究迭代器和生成器。
Symbol.hasInstance
之前我们用 instanceof 的时候,比如 a instaceof A
其实就是调用的A对象中的 Symbol.hasInstance 属性,它指向一个内部方法,现在 es6 拓展出来,我们可以自己定义啦。
先看看下面的代码,猜想输出什么?
class MyClass{ static [Symbol.hasInstance](num) { return num % 2 === 0 } [Symbol.hasInstance](num) { return num % 2 === 0 } } console.log(1 instanceof MyClass); // 序号(1) console.log(2 instanceof MyClass); // 序号(2) console.log(2 instanceof new MyClass()); // 序号(3) const obj = { [Symbol.hasInstance](obj) { return num % 2 === 0 } } console.log(1 instanceof obj);// 序号(4)
序号(1) MyClass 类上有静态方法 [Symbol.hasInstance],1 被当做参数传入 function,返回结果 false。 序号(2) 同理 true。
序号(3) 后面是实例化的对象,通过原型链查找到原型上 [Symbol.hasInstance],然后传入 2 执行,true。序号(4) 是普通对象内部存在这个方法,执行返回 false。
Symbol.match
意味着我们 'abc'.match(/a/) 调用的就是 RegExp[Symbol.match]。
也就是 String.prototype.match(regexp)
等价于 Regexp[Symbol.match](this)
const obj = { [Symbol.match](string) { console.log(string); //'b' return 'abcdefg'.indexOf(string) } } console.log('b'.match(obj)); // 1
Symbol.replace
String.prototype.replace(searchValue, replaceValue)
等价于 searchValue[Symbol.replace](this,replaceValue)
let str = 'abc'; console.log(str.replace(/a/,'A')); //'Abc' class regObj { [Symbol.replace](str, param) { console.log(str, param); // } } str.replace(new regObj, 'A')
划重点
这里我们模拟了一个 Reg 对象,str 调用 replace 的时候,第一个参数本是 reg 实例,而 JS 内置的 class RegExp 上有 [Symbol.replace],才调用对用方法。我们传入一个我们模拟的 Reg 的实例,而这个实例的所属类的原型,也有 [Symbol.replace],所以就调用了我们定义的方法。console 得以打印。
Symbol.split, Symbol.search 以以上两个都是字符串中与正则有密切联系的,因为他们的参数都可以是正则表达式。具体代码我就不列举了。
这些与正则表达式交互的方法,在 ES6 之前其实现细节是对开发者隐藏的,使得开发者无法将自定义对象模拟成正则表达式(并将它们传递给字符串的这些方法)。而 ES6 定义了 4 个符号以及对应的方法,将原生行为外包到内置的 RegExp 对象上。引用 也就是我们可以模拟一个内置的RegExp对象,只要他有这四个Symbol属性,那字符串调用match等方法并传入这个对象的时候,就可以找到对应的方法了。
Symbol.isConcatSpreadable
指向一个布尔值,可以使我们的对象被数组的 concat 拼接成数组,话不多说直接上例子
const concatObj = { 0: 'b', 1: 'c', length: 2, // 不可缺少 [Symbol.isConcatSpreadable]: true } const result = ['a'].concat(concatObj) console.log(result); //['a', 'b', 'c']
应该还是比较好理解的。
Symbol.toStringTag
我们都知道,以前我们经常用 Object.prototype.toString
检测对象具体的数据类型,Array还是Object,因为他是更精确更准确的。区别于自带的toString方法,如array.toString()只能返回一个元素拼接的字符串。以至于之前看的很多js库都会有这样的检测类型的方法
function isArray(value) { return Object.prototype.toString.call(value) === "[object Array]"; }
ES6 通过 Symbol.toStringTag 重定义了此行为,我们可以自定义它的返回结果,而不只是之前的几种。
class Mime { constructor() { this.name = 'syj' } toString() { return this.name } get [Symbol.toStringTag]() { return 'Handsome' } } const me = new Mime() console.log(me.toString()); //'syj' console.log(Object.prototype.toString.call(me)); //'[object Handsome]'
Symbol.unscopables
with 语句本来是为了减少代码重复的,不过因为负面性能,难理解易出错,es6 严格模式下已经禁止使用了。但是 es6 为了向下兼容,需要寻找方法使之前用 with 的代码能正常工作。
console.log(Array.prototype[Symbol.unscopables]) // { // copyWithin: true, // entries: true, // fill: true, // find: true, // findIndex: true, // keys: true, // values: true // }
这些 es6 的关键字都会被 with 环境排除,假如说有 values 的变量,不会访问到数组原型上。不过我几乎不用 with,只做了解吧...
Symbol.toPrimitive
该属性定义在 JS 数据类型的原型上,当发生基本数据类型的转化的时候,对象->基本数据类型,会调用此方法。此方法会接受一个参数,这个参数是被内部的运算类型规定的。参数有三种类型:number、string、default。直接上例子可能更清楚一些。
const obj = { [Symbol.toPrimitive](value) { switch (value) { case 'number': return 11 break; case 'string': return 'str'; case 'default': return 'default' default: throw new Error(); } } } //default: console.log(1 + obj); console.log(obj == 'default'); console.log(obj == 1); //number: console.log(1 - obj); console.log(obj / 2); console.log(2 * obj); //str: console.log(String(obj));
推荐去了解一下 js 中的强势类型转换和运算符的自动转换。
Symbol.species
用于产生派生对象的构造器。指向当前对象的构造函数,存在 Symbol.species 会调用它对应的方法,使用该方法返回的函数当做构造函数。引用
class MyArray extends Array { // 覆盖父类 Array 的构造函数 static get [Symbol.species]() { return Array; } }
总之这些东西在 es6 中提出,就是为了暴露它内部的实现,允许使用符号类型的原型属性来定义某些对象的基础行为。使我们可以通过模拟达到类似的效果。
iterator 会在接下来的清明假期详细研究,新开一个 md,包括 Iteration protocols,iterable,iterator,generator 这些概念。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
@yozman 嗯。确实, string 不是必要的。我说的可读只是因为变量名是大写的不好辨认
@sunyongjian
一般情况下为了便于查找
type
是定义在一个文件里的由于
string
是根据变量名来的,本身就保证了唯一性所以用不用
Symbol
是一样的,这里用
Symbol
的好处是可以不用写这种愚蠢的代码。
况且可读性的话,变量名已经能体现出来了。
所以在 redux action type 这种场景下我觉得是多余的
@yozman 为什么呢。 传不传 string,symbol 都是唯一的。string 增加了可读性
定义
redux action type
的时候