-
Notifications
You must be signed in to change notification settings - Fork 54
Description
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这些概念。