ES5中包含5种原始类型:字符串、数值、布尔值、null、undefined。ES6引入了第6种原始类型——Symbol。jquery
ES5的对象属性名都是字符串,很容易形成属性名冲突。好比,使用了一个他人提供的对象,想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。若是有一种机制保证每一个属性的名字都是独一无二的,这样就从根本上防止了属性名冲突。这就是ES6引入Symbol
的缘由。数组
Symbol 值经过Symbol
函数生成。这就是说,对象的属性名能够有两种类型:一种是字符串,另外一种是Symbol类型。凡是属性名属于 Symbol 类型,就都是独一无二的,能够保证不会与其余属性名产生冲突app
let firstName = Symbol(); let person = {}; person[firstName] = "huochai"; console.log(person[firstName]); // "huochai"
注意:Symbol
函数前不能使用new
命令,不然会报错。由于生成的 Symbol 是一个原始类型的值,不是对象函数
Symbol函数接受一个可选参数,能够添加一段文原本描述即将建立的Symbol,这段描述不可用于属性访问,可是建议在每次建立Symbol时都添加这样一段描述,以便于阅读代码和调试Symbol程序ui
let firstName = Symbol("first name"); let person = {}; person[firstName] = "huochai"; console.log("first name" in person); // false
console.log(person[firstName]); // "huochai"
console.log(firstName); // "Symbol(first name)"
Symbol的描述被存储在内部[[Description]]属性中,只有当调用Symbol的toString()方法时才能够读取这个属性。在执行console.log()时隐式调用了firstName的toString()方法,因此它的描述会被打印到日志中,但不能直接在代码里访问[[Description]]this
【类型检测】编码
Symbol是原始值,ES6扩展了typeof操做符,返回"symbol"。因此能够用typeof来检测变量是否为symbol类型spa
let symbol = Symbol("test symbol"); console.log(typeof symbol); // "symbol"
因为每个Symbol值都是不相等的,这意味着Symbol值能够做为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的状况很是有用,能防止某一个键被不当心改写或覆盖prototype
全部使用可计算属性名的地方,均可以使用Symbol3d
let firstName = Symbol("first name"); // 使用一个需计算字面量属性
let person = { [firstName]: "huochai" }; // 让该属性变为只读
Object.defineProperty(person, firstName, { writable: false }); let lastName = Symbol("last name"); Object.defineProperties(person, { [lastName]: { value: "match", writable: false } }); console.log(person[firstName]); // "huochai"
console.log(person[lastName]); // "match"
在此示例中,首先经过可计算对象字面量属性语法为person对象建立了个Symbol属性firstName。后面一行代码将这个属性设置为只读。随后,经过Object.defineProperties()方法建立一个只读的Symbol属性lastName,此处再次使用了对象字面量属性,但倒是做为object.defineProperties()方法的第二个参数使用
注意:Symbol 值做为对象属性名时,不能用点运算符
var mySymbol = Symbol(); var a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
由上面结果看出,a.mySymbol和a['mySymbol']里的mySymbol是字符串类型的属性名,a[mySymbol]里的mySymbol才是Symbol类型的属性名。虽然都叫mySymbol,但值不相同
尽管在全部使用可计算属性名的地方,均可以使用Symbol来代替,可是为了在不一样代码片断间有效地共享这些Symbol,须要创建一个体系
有时但愿在不一样的代码中共享同一个Symbol,例如,在应用中有两种不一样的对象类型,可是但愿它们使用同一个Symbol属性来表示一个独特的标识符。通常而言,在很大的代码库中或跨文件追踪Symbol很是困难并且容易出错,出于这些缘由,ES6提供了一个能够随时访问的全局Symbol注册表
一、Symbol.for()
若是想建立一个可共享的Symbol,要使用Symbol.for()方法。它只接受一个参数,也就是即将建立的Symbol的字符串标识符,这个参数一样也被用做Symbol的描述
let uid = Symbol.for("uid"); let object = {}; object[uid] = "12345"; console.log(object[uid]); // "12345"
console.log(uid); // "Symbol(uid)"
Symbol.for()方法首先在全局Symbol注册表中搜索键为"uid"的Symbol是否存在。若是存在,直接返回已有的Symbol,不然,建立一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随即返回新建立的Symbol。后续若是再传入一样的键调用Symbol.for()会返回相同的Symbol
let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; console.log(object[uid]); // "12345"
console.log(uid); // "Symbol(uid)"
let uid2 = Symbol.for("uid"); console.log(uid === uid2); // true
console.log(object[uid2]); // "12345"
console.log(uid2); // "Symbol(uid)
在这个示例中,uid和uid2包含相同的Symbol而且能够互换使用。第一次调用Symbol.for()方法建立这个Symbol,第二次调用能够直接从Symbol的全局注册表中检索到这个Symbol
二、Symbol.keyFor()
还有一个与Symbol共享有关的特性:能够使用Symbol.keyFor()方法在Symbol全局注册表中检索与Symbol有关的键
let uid = Symbol.for("uid"); console.log(Symbol.keyFor(uid)); // "uid"
let uid2 = Symbol.for("uid"); console.log(Symbol.keyFor(uid2)); // "uid"
let uid3 = Symbol("uid"); console.log(Symbol.keyFor(uid3)); // undefined
uid和uid2都返回了"uid"这个键,而在Symbol全局注册表中不存在uid3这个Symbol,也就是不存在与之有关的键,因此最终返回undefined
注意:Symbol.for
为Symbol值登记的名字,是全局环境的,能够在不一样的 iframe 或 service worker 中取到同一个值
let iframe = document.createElement('iframe'); iframe.src = String(window.location); document.body.appendChild(iframe); console.log(iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo'));// true
上面代码中,iframe 窗口生成的 Symbol 值,能够在主页面获得
Symbol全局注册表是一个相似全局做用域的共享环境,也就是说不能假设目前环境中存在哪些键。当使用第三方组件时,尽可能使用Symbol键的命名空间以减小命名冲突。例如,jQuery的代码能够为全部键添加"jquery"前缀,就像"jquery.element"或其余相似的键
类型转换是JS中的一个重要语言特性,然而其余类型没有与Symbol逻辑等价的值,于是Symbol使用起来不是很灵活
使用console.log()方法来输出Symbol的内容,它会调用Symbol的String()方法并输出有用的信息。也能够像这样直接调用string()方法来得到相同的内容
let uid = Symbol.for("uid"), desc = String(uid); console.log(desc); // "Symbol(uid)"
String()函数调用了uid.toString()方法,返回字符串类型的Symbol描述里的内容。可是,若是尝试将Symbol与一个字符串拼接,会致使程序抛出错误
let uid = Symbol.for("uid"), desc = uid + ""; // 引起错误!
将uid与空字符串拼接,首先要将uid强制转换为一个字符串,而Symbol不能够被转换为字符串,故程序直接抛出错误
一样,也不能将Symbol强制转换为数字类型。将Symbol与每个数学运算符混合使用都会致使程序抛出错误
let uid = Symbol.for("uid"), sum = uid / 1; // 引起错误!
尝试将Symbol除1,程序直接抛出错误。并且不管使用哪个数学操做符,都没法正常运行
注意:布尔值除外,由于Symbol与JS中的非空值相似,其等价布尔值为true
let uid = Symbol.for("uid"); console.log(uid);//'Symbol(uid)'
console.log(!uid);//false
console.log(Boolean(uid));//true
Symbol做为属性名,该属性不会出如今for...in、for...of循环中,也不会被Object.getOwnPropertyNames()、Object.keys()、JSON.stringify()返回。因而,在ES6中添加了一个Object.getOwnpropertySymbols()方法来检索对象中的Symbol属性
Object.getOwnPropertySymbols()方法的返回值是一个包含全部Symbol自有属性的数组
let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; let symbols = Object.getOwnPropertySymbols(object); console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(object[symbols[0]]); // "12345"
在这段代码中,object对象有一个名为uid的Symbol属性,object.getOwnPropertySymbols()方法返回了包含这个属性的数组
另外一个新的API——Reflect.ownKeys()
方法能够返回全部类型的键名,包括常规键名和 Symbol 键名
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; console.log(Reflect.ownKeys(obj));// ["enum", "nonEnum", Symbol(my_key)]
因为以 Symbol 值做为名称的属性,不会被常规方法遍历获得。能够利用这个特性,为对象定义一些非私有的、但又但愿只用于内部的方法
var size = Symbol('size'); class Collection { constructor() { this[size] = 0; } add(item) { this[this[size]] = item; this[size]++; } static sizeOf(instance) { return instance[size]; } } var 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)]
上面代码中,对象x的size属性是一个Symbol值,因此Object.keys(x)、Object.getOwnPropertyNames(x)都没法获取它。这就形成了一种非私有的内部方法的效果
除了定义本身使用的Symbol值之外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法
一、Symbol.haslnstance:一个在执行instanceof时调用的内部方法,用于检测对象的继承信息
二、Symbol.isConcatSpreadable:一个布尔值,用于表示当传递一个集合做为Array.prototype.concat()方法的参数时,是否应该将集合内的元素规整到同一层级
三、Symbol.iterator:一个返回迭代器的方法
四、Symbol.match:一个在调用String.prototype.match()方法时调用的方法,用于比较字符串
五、Symbol.replace:一个在调用String.prototype.replace()方法时调用的方法,用于替换字符串的子串
六、Symbol.search:一个在调用String.prototype.search()方法时调用的方法,用于在字符串中定位子串
七、Symbol.species:用于建立派生类的构造函数
八、Symbol.split:一个在调用String.prototype.split()方法时调用的方法,用于分割字符串
九、Symbol.toprimitive:一个返回对象原始值的方法
十、Symbol.ToStringTag:一个在调用Object.prototype.toString()方法时使用的字符串,用于建立对象描述
十一、Symbol.unscopables:一个定义了一些不可被with语句引用的对象属性名称的对象集合
一、【Symbol.haslnstance】
每一个函数都有一个Symbol.haslnstance方法,用于肯定对象是否为函数的实例。该方法在Function.prototype中定义,全部函数都继承了instanceof属性的默认行为。为了确保Symbol.haslnstance不会被意外重写,该方法被定义为不可写、不可配置而且不可枚举
Symbol.haslnstance方法只接受一个参数,即要检查的值。若是传入的值是函数的实例,则返回true
obj instanceof Array; //等价于下面这行
Array[Symbol.hasInstance](obj);
本质上,ES6只是将instanceof操做符从新定义为此方法的简写语法。如今引入方法调用后,就能够随意改变instanceof的运行方式了
class MyClass { [Symbol.hasInstance](foo) { return foo instanceof Array; } } console.log([1, 2, 3] instanceof new MyClass()); // true
假设定义一个无实例的函数,就能够将Symbol.haslnstance的返回值硬编码为false
function MyObject() { // ...
} Object.defineProperty(MyObject, Symbol.hasInstance, { value: function(v) { return false; } }); let obj = new MyObject(); console.log(obj instanceof MyObject); // false
只有经过Object.defineProperty()方法才可以改写一个不可写属性,上面的示例调用这个方法来改写symbol.haslnstance,为其定义一个老是返回false的新函数,即便obj实际上确实是Myobject类的实例,在调用过object.defineProperty()方法以后,instanceof运算符返回的也是false
固然,也能够基于任意条件,经过值检查来肯定被检测的是否为实例。例如,能够将1~100的数字定义为一个特殊数字类型的实例,具体实现的代码以下
function SpecialNumber() { // empty
} Object.defineProperty(SpecialNumber, Symbol.hasInstance, { value: function(v) { return (v instanceof Number) && (v >=1 && v <= 100); } }); let two = new Number(2), zero = new Number(0); console.log(two instanceof SpecialNumber); // true
console.log(zero instanceof SpecialNumber); // false
在这段代码中定义了一个symbol.hasInstance方法,当值为Number的实例且其值在1~100之间时返回true。因此即便SpecialNumber函数和变量two之间没有直接关系,变量two也被确认为specialNumber的实例
若是要触发Symbol.haslnstance调用,instanceof的左操做数必须是一个对象,若是左操做数为非对象会致使instanceof老是返回false
固然,能够重写全部内建函数(如Date和Error函数)默认的symbol.haslnstance属性。可是这样作的后果是代码的运行结果变得不可预期且有可能使人感到困惑,因此不推荐这样作,最好的作法是,只在必要状况下改写本身声明的函数的Symbol.haslnstance属性
二、【Symbol.isConcatSpreadable】
对象的Symbol.isConcatSpreadable属性是布尔值,表示该对象使用Array.prototype.concat()时,是否能够展开
let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
上面代码说明,数组的默认行为是能够展开。Symbol.isConcatSpreadable
属性等于undefined或true,都有这个效果
类数组对象也能够展开,但它的Symbol.isConcatSpreadable
属性默认为false
,必须手动打开
let obj = {length: 2, 0: 'c', 1: 'd'}; ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
obj[Symbol.isConcatSpreadable] = true; ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
对于一个类来讲,Symbol.isConcatSpreadable
属性必须写成实例的属性
class A1 extends Array { constructor(args) { super(args); this[Symbol.isConcatSpreadable] = true; } } class A2 extends Array { constructor(args) { super(args); this[Symbol.isConcatSpreadable] = false; } } let a1 = new A1(); a1[0] = 3; a1[1] = 4; let a2 = new A2(); a2[0] = 5; a2[1] = 6; [1, 2].concat(a1).concat(a2) // [1, 2, 3, 4, [5, 6]]
上面代码中,类A1
是可展开的,类A2
是不可展开的,因此使用concat
时有不同的结果
三、【Symbol.species】
对象的Symbol.species
属性,指向当前对象的构造函数。创造实例时,默认会调用这个方法,即便用这个属性返回的函数看成构造函数,来创造新的实例对象
class MyArray extends Array { // 覆盖父类 Array 的构造函数
static get [Symbol.species]() { return Array; } }
上面代码中,子类MyArray
继承了父类Array
。建立MyArray
的实例对象时,原本会调用它本身的构造函数,可是因为定义了Symbol.species
属性,因此会使用这个属性返回的的函数,建立MyArray
的实例
这个例子也说明,定义Symbol.species
属性要采用get
读取器。默认的Symbol.species
属性等同于下面的写法
static get [Symbol.species]() { return this; }
class MyArray extends Array { static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); mapped instanceof MyArray // false
mapped instanceof Array // true
上面代码中,因为构造函数被替换成了Array
。因此,mapped
对象不是MyArray
的实例,而是Array
的实例
四、【Symbol.match】
对象的Symbol.match
属性,指向一个函数。当执行str.match(myObject)
时,若是该属性存在,会调用它,返回该方法的返回值
String.prototype.match(regexp) // 等同于
regexp[Symbol.match](this) class MyMatcher { [Symbol.match](string) { return 'hello world'.indexOf(string); } } 'e'.match(new MyMatcher()) // 1
五、【Symbol.replace】
对象的Symbol.replace
属性,指向一个方法,当该对象被String.prototype.replace
方法调用时,会返回该方法的返回值
String.prototype.replace(searchValue, replaceValue) // 等同于
searchValue[Symbol.replace](this, replaceValue)
六、【Symbol.search】
对象的Symbol.search
属性,指向一个方法,当该对象被String.prototype.search
方法调用时,会返回该方法的返回值
String.prototype.search(regexp) // 等同于
regexp[Symbol.search](this) class MySearch { constructor(value) { this.value = value; } [Symbol.search](string) { return string.indexOf(this.value); } } 'foobar'.search(new MySearch('foo')) // 0
七、【Symbol.split】
对象的Symbol.split
属性,指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值
String.prototype.split(separator, limit) // 等同于
separator[Symbol.split](this, limit)
class MySplitter { constructor(value) { this.value = value; } [Symbol.split](string) { var index = string.indexOf(this.value); if (index === -1) { return string; } return [ string.substr(0, index), string.substr(index + this.value.length) ]; } } 'foobar'.split(new MySplitter('foo'))// ['', 'bar']
'foobar'.split(new MySplitter('bar'))// ['foo', '']
'foobar'.split(new MySplitter('baz'))// 'foobar'
上面方法使用Symbol.split
方法,从新定义了字符串对象的split
方法的行为
八、【Symbol.iterator】
对象的Symbol.iterator
属性,指向该对象的默认遍历器方法
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
对象进行for...of
循环时,会调用Symbol.iterator
方法,返回该对象的默认遍历器
class Collection { *[Symbol.iterator]() { let i = 0; while(this[i] !== undefined) { yield this[i]; ++i; } } } let myCollection = new Collection(); myCollection[0] = 1; myCollection[1] = 2; for(let value of myCollection) { console.log(value); } // 1 // 2
九、【Symbol.toPrimitive】
对象的Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值
Symbol.toPrimitive
被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式
一、Number:该场合须要转成数值
二、String:该场合须要转成字符串
三、Default:该场合能够转成数值,也能够转成字符串
let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } } }; 2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
十、【String.toStringTag】
对象的Symbol.toStringTag
属性,指向一个方法。在该对象上面调用Object.prototype.toString
方法时,若是这个属性存在,它的返回值会出如今toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性能够用来定制[object Object]
或[object Array]
中object
后面的那个字符串
// 例一
({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]" // 例二
class Collection { get [Symbol.toStringTag]() { return 'xxx'; } } var x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]"
ES6新增内置对象的Symbol.toStringTag
属性值以下:
JSON[Symbol.toStringTag]:'JSON' Math[Symbol.toStringTag]:'Math' Module[Symbol.toStringTag]:'Module' ArrayBuffer.prototype[Symbol.toStringTag]:'ArrayBuffer' DataView.prototype[Symbol.toStringTag]:'DataView' Map.prototype[Symbol.toStringTag]:'Map' Promise.prototype[Symbol.toStringTag]:'Promise' Set.prototype[Symbol.toStringTag]:'Set'
%TypedArray%.prototype[Symbol.toStringTag]:'Uint8Array' WeakMap.prototype[Symbol.toStringTag]:'WeakMap' WeakSet.prototype[Symbol.toStringTag]:'WeakSet'
%MapIteratorPrototype%[Symbol.toStringTag]:'Map Iterator'
%SetIteratorPrototype%[Symbol.toStringTag]:'Set Iterator'
%StringIteratorPrototype%[Symbol.toStringTag]:'String Iterator' Symbol.prototype[Symbol.toStringTag]:'Symbol' Generator.prototype[Symbol.toStringTag]:'Generator' GeneratorFunction.prototype[Symbol.toStringTag]:'GeneratorFunction'
十一、【Symbol.unscopables】
对象的Symbol.unscopables
属性,指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除。