ES6引入Symbol的緣由:防止屬性名的衝突(ES5的對象屬性名都是字符串,容易形成屬性名的衝突)。javascript
ES6引入一種新的原始數據類型:Symbol,表示獨一無二的值。它是javascript語言的第七種數據類型,前六種是:Undefined
、Null
、Boolean
、String
、Number
、Object
。java
Symbol值是經過Symbol
函數生成的。chrome
let s = Symbol(); typeof s //"symbol"
注:Symbol函數前不能使用new命令,不然會報錯。報錯的緣由就是:生成的Symbol是一個原始類型的值,而不是對象,因此不能添加屬性。它是一種相似於字符串的數據類型。數組
Symbol
函數能夠接受一個字符串做爲參數,表示對Symbol實例的描述,主要是爲了在控制檯顯示,或者轉爲字符串時,比較容易區分。app
var s1 = Symbol('foo'); var s2 = Symbol('bar'); s1 // Symbol(foo) s2 // Symbol(bar) s1.toString() // "Symbol(foo)" s2.toString() // "Symbol(bar)"
注:Symbol函數的參數只是表示對當前Symbol值的描述,所以相同參數的Symbol函數的返回值是不相等的。函數
// 沒有參數的狀況 var s1 = Symbol(); var s2 = Symbol(); s1 === s2 // false // 有參數的狀況 var s1 = Symbol("foo"); var s2 = Symbol("foo"); s1 === s2 // false
Symbol值不能與其餘類型的值進行運算。this
var sym = Symbol('My symbol'); "your symbol is " + sym; // Error: Cannot convert a Symbol value to a string
可是Symbol值能夠轉爲字符串。prototype
var sym = Symbol('My symbol'); String(sym) // 'Symbol(My symbol)' sym.toString() // 'Symbol(My symbol)'
二、做爲屬性名的Symbol
因爲每個Symbol值都是不相等的,這意味着Symbol值能夠做爲標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的狀況很是有用,能防止某一個鍵被不當心改寫或覆蓋。debug
var mySymbol = Symbol(); // 第一種寫法 var a = {}; a[mySymbol] = 'Hello!'; // 第二種寫法 var a = { [mySymbol]: 'Hello!' }; // 第三種寫法 var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上寫法都獲得一樣結果 a[mySymbol] // "Hello!"
Object.defineProperty
用來將對象的屬性名指定爲一個Symbol值。3d
注: Symbol值做爲對象屬性名時,不能用點運算符。
var mySymbol = Symbol(); var a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"
上面代碼中,由於點運算符後面老是字符串,因此不會讀取mySymbol做爲標識名所指代的那個值,致使a的屬性名其實是一個字符串,而不是一個Symbol值。
同理,在對象的內部,使用Symbol值定義屬性時,Symbol值必須放在方括號之中。
let s = Symbol(); let obj = { [s]: function (arg) { ... } }; //等同於 let obj = { [s](arg) { ... } }; obj[s](123);
上面代碼中,若是s不放在方括號中,該屬性的鍵名就是字符串s,而不是s所表明的那個Symbol值。
Symbol類型還能夠定義一組常量,使這組常量的值不想等。
var log = {}; log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn'), }; console.log(log.levels.DEBUG, 'debug message');//Symbol(debug) 'debug message' console.log(log.levels.INFO, 'info message');//Symbol(info) 'info message'
須要注意的是:Symbol值做爲屬性名時,該屬性仍是公開屬性,不是私有屬性。
三、屬性名的遍歷
Symbol做爲屬性名,該屬性不會出如今for...in
、for...of
循環中,也不會被Object.keys()
、Object.getOwnPropertyNames()
返回。可是,它也不是私有屬性,有一個Object.getOwnPropertySymbols
方法,能夠獲取指定對象的全部Symbol屬性名。
注:這裏涉及到兩個對象方法,它們的做用是:
Object.getOwnPropertyNames
:獲取全部的屬性名,不包括prototy中的屬性,返回一個數組。
var o = Object.create({ "say": function () { alert(this.name); }, "name":"Byron" }); Object.defineProperties(o, { 'age': { value: 24, writable: true, enumerable: true, configurable: true }, 'sex': { value: 'male', writable: false, enumerable: false, configurable: false } }); console.log(o); //結果以下圖 console.log(Object.getOwnPropertyNames(o));//["age", "sex"]
上面代碼console.log(o)的結果
從上面的例子中能夠看到prototype中的name屬性沒有獲取到。
Object.keys()
和getOwnPropertyNames
方法相似,可是獲取全部的可枚舉的屬性,返回一個數組。
console.log(Object.keys(o)); //["age"]
Object.getOwnPropertySymbols
方法返回一個數組,成員是當前對象的全部用做屬性名的Symbol值。
var obj = {}; var foo = Symbol("foo"); Object.defineProperty(obj, foo, { value: "foobar", }); for (var i in obj) { console.log(i); // 無輸出 //可是直接在最新版本的chrome裏倒是輸出了{Symbol(foo): "foobar"} } Object.getOwnPropertyNames(obj) // [] Object.getOwnPropertySymbols(obj) // [Symbol(foo)]
有一個新的APIReflect.ownKeys
方法能夠返回全部類型的鍵名,包括常規的和Symbol。
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; 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.for(),Symbol.keyFor()Symbol.for
方法:接受一個字符串做爲參數,而且建立的Symbol值是全局的,若是咱們已經有一個Symbol值了,而且想從新使用,那麼就能夠利用這個方法來獲取。
因爲Symbol.for
建立的Symbol值是全局的,因此能夠在不一樣的iframe和service worker中取到同一個值,例如:
iframe = document.createElement('iframe'); iframe.src = String(window.location); document.body.appendChild(iframe); iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') // true
Symbol.for
的工做原理就是:搜索有沒有以該參數做爲名稱的Symbol值,若是有,就返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值。
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
下面咱們來對比一下:Symbol.for()
與 Symbol()
:
相同點:都會生成新的Symbol。
區別:
Item | 全局搜索 | 每次調用返回一個新的Symbol值 |
---|---|---|
Symbol.for() | 是 | 會先檢測給定的key是否存在,若是不存在則建立新的Symbol值 |
Symbol() | 否 | 是 |
Symbol.for("bar") === Symbol.for("bar") // true Symbol("bar") === Symbol("bar") // false
Symbol.keyFor
方法:返回一個已登記的Symbol值的key。
var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
五、內置的Symbol值
除了自定義的Symbol值外,ES6還提供了11個內置的Symbol值,它們都是對象的屬性,指向語言內部使用的方法。
(1)、Symbol.hasInstance
該對象使用instanceof
運算符時,會調用這個方法,判斷該對象是否爲某個構造函數的實例。
好比:foo instanceof Foo
在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)
。
class MyClass { [Symbol.hasInstance](foo) { return foo instanceof Array; } //對這一段代碼不是很理解 } var o = new MyClass(); o instanceof Array // false o instanceof MyClass // true
instanceof
運算符能夠用來判斷某個構造函數的prototype
屬性是否存在另一個要檢測對象的原型鏈上。用法:
object instanceof constructor
參數:object
:要檢測的對象constructor
:某個構造函數function C(){} //定義構造函數 function D(){} var o = new C(); o instanceof C; //true,由於:Object.getPrototypeOf(o) === C.prototype o instanceof D; // false,由於D.prototype不在o的原型鏈上 o instanceof Object; // true,由於Object.prototype.isPrototypeOf(o)返回true C.prototype instanceof Object // true,同上 C.prototype = {}; var o2 = new C(); o2 instanceof C; // true o instanceof C; // false,C.prototype指向了一個空對象,這個空對象不在o的原型鏈上. D.prototype = new C(); var o3 = new D(); o3 instanceof D; // true o3 instanceof C; // true
(2)、Symbol.isConcatSpreadable
對象的Symbol.isConcatSpreadable
屬性等於一個布爾值,表示該對象使用Array.prototype.concat()
時,屬性值爲true即爲能夠展開,false則不容許展開。
let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e'] 我執行的結果表示這個屬性沒有起做用
相似數組的對象,它的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 { [Symbol.isConcatSpreadable]() { return true; } } class A2 extends Array { [Symbol.isConcatSpreadable]() { return 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]] // 個人執行結果:[ 1, 2, A1 { '0': 3, '1': 4 }, A2 { '0': 5, '1': 6 } ]
(3)、Symbol.species
該對象做爲構造函數創造實例時,會調用這個方法。即若是this.constructor[Symbol.species]
存在,就會使用這個屬性做爲構造函數,來創造新的實例對象。
(4)、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
上面代碼,'e'.match(new MyMatcher())
執行後,Symbol.match
方法被調用,因此會返回字母e的位置時1。
(5)、Symbol.replace
當該對象被String.prototype.replace
方法調用時,會返回該方法的返回值。
String.prototype.replace(searchValue, replaceValue) // 等同於 searchValue[Symbol.replace](this, replaceValue)
class MyReplace{ constructor(val1,val2){ this.val1 = val1; this.val2 = val2; } [Symbol.replace](string){ return string.replace(new RegExp(this.val1),this.val2); } } 'hello world'.replace(new MyReplace('world','jumei')); // hello jumei
(6)、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
(7)、Symbol.split
當該對象被String.prototype.split
方法調用時,會返回該方法的返回值。
String.prototype.split(separator, limit) // 等同於 separator[Symbol.split](this, limit)
class MySplit{ constructor(val1,val2){ this.val1 = val1; this.val2 = val2; } [Symbol.split](string){ return string.split(this.val1,this.val2); } } 'How are you ?'.split(new MySplit(" ",3)); //[ 'How', 'are', 'you' ]
(8)、Symbol.iterator
對象的Symbol.iterator
屬性,指向該對象的默認遍歷器方法,即該對象進行for...of
循環時,會調用這個方法,返回該對象的默認遍歷器,詳細介紹參見《Iterator和for...of循環》一章。
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
(9)、Symbol.toPrimitive
該對象被轉爲原始類型的值時,會調用這個方法,返回該對象對應的原始類型值。
JavaScript中的原始類型包括數字,字符串和布爾值。
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 個人執行結果:NaN 3 + obj // '3default' 個人執行結果:3[object Object] obj === 'default' // true 個人執行結果:false String(obj) // 'str' 個人執行結果:[object Object]
看來這個屬性如今或許還不支持吧,才形成個人運行結果是這樣的。
(10)、Symbol.toStringTag
在該對象上面調用Object.prototype.toString
方法時,若是這個屬性存在,它的返回值會出如今toString
方法返回的字符串之中,表示對象的類型。
也就是說,這個屬性能夠用來定製[object Object]
或[object Array]
中object後面的那個字符串。
({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]" //等同於 class Collection { get [Symbol.toStringTag]() { return 'Foo'; } } var x = new Collection(); Object.prototype.toString.call(x) // "[object Foo]"
ES6新增內置對象的Symbol.toStringTag
屬性值以下。
JSON[Symbol.toStringTag]:'JSON'
Math[Symbol.toStringTag]:'Math'
Module對象M[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'
(11)、Symbol.unscopables
該對象指定了使用with
關鍵字時,哪些屬性會被with
環境排除。
Array.prototype[Symbol.unscopables] // { // copyWithin: true, // entries: true, // fill: true, // find: true, // findIndex: true, // keys: true // } Object.keys(Array.prototype[Symbol.unscopables]) // ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']
上面代碼說明,數組有6個屬性,會被with
命令排除。
// 沒有unscopables時 class MyClass { foo() { return 1; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 1 } // 有unscopables時 class MyClass { foo() { return 1; } get [Symbol.unscopables]() { return { foo: true }; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 2 }