es5定義屬性名只能使用字符串,這就可能致使一個問題,就是你定義的屬性名跟別人定義的屬性名衝突。爲了解決這個問題,es6引入了一個新的原始數據類型Symbol。Symbol的值是經過Symbol函數生成的,如今的屬性名能夠有兩種類型,一種是字符串,另外一種就是Symbol類型,凡是Symbol類型的屬性名就是獨一無二的,不會與其它任何屬性名衝入。html
Symbol概述es6
定義一個Symbol類型的變量看看編程
嗯,變量s果真是Symbol類型,不是字符串了。數組
Symbol是一種原始數據類型,不是對象,因此Symbol不能new(會報錯),Symbol不能添加屬性和方法。基本上,它是一種相似於字符串的數據類型。promise
Symbol函數能夠接受一個字符串做爲參數,表示對 Symbol 實例的描述。
let s1 = Symbol('foo');函數
let s2 = Symbol('bar'); flex
s1; //Symbol(foo)this
s2; //Symbol(bar) es5
let s3 = Symbol('foo');spa
s1 和 s3是不相等的兩個值。
Symbol還有如下一些特徵,Symbol能夠顯示轉爲字符串
s1.toString(); // "Symbol(foo)"
若是 Symbol 的參數是一個對象,就會調用該對象的toString方法,將其轉爲字符串,而後才生成一個 Symbol 值。
var a = {};
var b = Symbol(a);
b; // "Symbol([object Object])"
a['toString'] = function () { return 'a symbol' }
var c = Symbol(a);
c; // "Symbol(a symbol)"
也能夠轉爲布爾類型
Boolean(s1); // true
可是不能轉爲數字,也不能與其它數據類型參與計算,會報錯。
獲取Symbol的描述,能夠經過 Symbol.prototype提供的description屬性獲取
s1.description //"foo"
------------------------------------
做爲屬性名的Symbol
經過方括號結構和Object.defineProperty將對象的屬性名指定爲一個Symbol值。
var mySymbol = Symbol();
//第一種寫法
var a = {};
a[mySymbol] = 'hello';
//第二種寫法
var a = {
[mySymbol]: 'hello'
};
//第三種寫法
var a = {};
Object.defineProperty(a, mySymbol, {value: 'hello'});
//注意,Symbol值做爲對象屬性名時不能用點運算符。
使用Symbol做爲屬性名不會出現同名的屬性
1. let obj = {
type: 'PC', //小A定義的
type: 'NEW' //小B定義的,把小A的覆蓋了
}
2. const type = 'TYPE'; //小A定義的
const type1 = 'TYPE'; //小B定義的
let obj = {
[type]: 'PC',
[type1]: 'NEW' //把小A的覆蓋了
}
3. 使用Symbol不會被覆蓋
const type = Symbol('type'); //小A定義的
const type1 = Symbol('type'); //小B定義的
let obj = {
[type]: 'PC',
[type1]: 'NEW'
}
------------------------------------消除魔術字符串
魔術字符串指的是,在代碼之中屢次出現、與代碼造成強耦合的某一個具體的字符串或者數值。風格良好的代碼,應該儘可能消除魔術字符串,改由含義清晰的變量代替。
'Triangle'就是一個魔術字符串,它出現屢次,不利於維護,因此咱們要把他定義成一個變量或者屬性,消除強耦合。
triangle:Symbol('triangle') //這裏若是triangle的值是什麼並不重要,只是一個標識,不會與業務耦合
------------------------------------
屬性名的遍歷
在a.js文件中定義一個對象testSymbol,並將它對外暴露
let t2 = Symbol('t2');
export let testSymbol = {
t1: 't1value',
[t2]: 't2value'
};
在b.js中引入a.js,只能訪問到屬性t1, 沒法訪問到t2屬性
import { testSymbol } from '/a.js'
console.log(testSymbol['t1']); //t1value
console.log(testSymbol[t2]); //Uncaught (in promise) ReferenceError: t2 is not defined
for(let i in testSymbol) { console.log(i); } //"t1"
因此能夠利用Symbol定義私有屬性
-----------------------------------
Symbol.for() Symbol.keyFor()
Symbol()和Symbol.for()這兩個方法都會生產Symbol對象,Symbol.for()與Symbol()的不一樣是它生成的Symbol對象會被註冊到全局環境中。Symbol.for()不會每次調用就返回一個新的 Symbol 類型的值,而是會先檢查給定的key是否已經存在,若是不存在纔會新建一個值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2); // true
Symbol.for('foo')先檢查全局的註冊表中有沒有key是'foo'的Symbol對象,若是存在就返回這個對象,如不不存在就從新生成。
------------------------------------
內置的Symbol值
es6提供了11個內置的Symbol值,指向了語言內部使用的方法,實現元編程(對js默認的行爲作操做)。
1. Symbol.hasInstance
對象的Symbol.hasInstance指向內部方法。當對其它對象(a)使用instanceof操做符判斷是否是該對象(b)的實例時,會調用該對象(b)的[Symbol.hasInstance
]方法。好比a instanceof b,在方法內部實際調用的是b[Symbol.hasInstance](a)。
let a = [1,2,3], b;
class MyArray {
[Symbol.hasInstance] (val) {
return val instanceof Array;
}
}
b = new MyArray();
console.log(a instanceof b); //true
2.Symbol.isConcatSpreadable
對象的Symbol.isConcatSpreadable屬性是一個布爾值,它表示對該對象使用Array.prototype.concat()方法時,該對象是否能夠展開。
當對象是數組時,Symbol.isConcatSpreadable的默認值是undefined,默認展開。設置爲false不能夠展開,設置爲true能夠展開。
當對象是一個相似數組的對象時,Symbol.isConcatSpreadable的默認值是undefined,默認不展開。設置爲false不能夠展開,設置爲true能夠展開。
默認狀況:
let a1 = [3, 4];
let a2 = {0: 3, 1: 4, length: 2};
console.log([1].concat(a1, [5, 6])); // [1, 3, 4, 5, 6]
console.log([1].concat(a2, [5, 6])); // [1, {0: 3, 1: 4, length: 2}, 5, 6]
設置一下:
a1[Symbol.isConcatSpreadable] = false;
a2[Symbol.isConcatSpreadable] = true;
console.log([1].concat(a1, [5, 6])); // [1, 3, [4, 5], 6]
console.log([1].concat(a2, [5, 6])); // [1, 3, 4, 5, 6]
Symbol.isConcatSpreadable還能夠定義在類中
class A1 extends Array {
constructor (args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor (args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}
let a1Obj = new A1();
let a2Obj = new A2();
a1Obj[0] = 3;
a1Obj[1] = 4;
a2Obj[0] = 5;
a2Obj[1] = 6;
console.log([1, 2].concat(a1Obj, a2Obj)); //[1, 2, 3, 4, [5, 6]]
A1是定義在實例上,A2是定義在類自己。
3. Symbol.species
對象的Symbol.species屬性,指向構造函數。當建立衍生對象時,會調用這個屬性。
class MyArray extends Array {};
let ma1 = new MyArray(1, 2, 3);
let ma2 = ma1.map(item => item * 2);
let ma3 = ma1.filter(item => item > 1);
console.log(ma2 instanceof MyArray); //true
console.log(ma2 instanceof Array); //true
console.log(ma3 instanceof MyArray); //true
console.log(ma3 instanceof Array); //true
從上面例子看,類MyArray繼承Array,ma1是MyArray的實例,ma2和ma3是ma1的衍生對象。ma2和ma3都是經過數組的方法生成出來的對象,也就是說他們應該是Array的實例,可是他們並非MyAarry的實例,因此上面獲得的結果並非咱們想要的。那麼經過設置Symbol.species屬性就能夠解決這個問題,咱們要設置MyArray的Symbol.species屬性。
定義Symbol.species使用get取值器。Symbol.species是類的靜態屬性。Symbol.species內部會指向指定的構造函數(如今咱們須要指向Array)。
class MyArray extends Array {
static get [Symbol.species] () {
return Array;
}
};
經過上面的設置後,ma2和ma3只是Array的實例,並非MyArray的實例。
Symbol.species主要的用途是,有些類庫是在基類的基礎上修改的,那麼子類使用繼承的方法時,做者可能但願返回基類的實例,而不是子類的實例。
4.Symbol.match (Symbol.replace, Symbol.search,Symbol.split都是相似的)
對象的Symbol.match屬性,指向一個方法,當執行str.match(myObj)時,若是該屬性存在,會調用它,返回該方法的返回值。
class MyMatcher {
constructor (bstr) {
this.bstr = bstr;
}
[Symbol.match] (str) {
return str.indexOf(this.bstr);
}
}
'hello world'.match(new MyMatcher('e'));
5. Symbol.replace
class MyReplace {
constructor (bstr) {
this.bstr = bstr;
}
[Symbol.replace](oldStr, newStr){
console.log('"'+ oldStr +'"被替換成了"'+ newStr +'"');
return this.bstr.replace(oldStr, newStr);
}
}
let res = '一'.replace(new MyReplace('我有一盆花'), '不少');
console.log(res);
Symbol.replace方法會收到兩個參數,第一個參數是replace 方法正在做用的對象,上面例子第一個參數是「一」,第二個參數是替換後的值「不少」。
6. Symbol.search
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) // 0
7. Symbol.split
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
let 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'
8. Symbol.iterator
對象的Symbol.iterator屬性,指向該對象的默認遍歷器方法。對象執行for...of時,會調用該對象的Symbol.iterator方法,可參見Iterator 和 for...of 循環。
9. Symbol.toPrimitive
對象的Symbol.toPrimitive屬性,指向一個方法。把一個對象轉爲原始類型值時。會調用Symbol.toPrimitive方法,返回值是該對象的原始類型值。Symbol.toPrimitive接受一個參數,該參數表示當前運算的模式,一共有三種模式:
Number:該場合須要轉成數值
String:該場合須要轉成字符串
Default: 該場合能夠轉成數值,也能夠轉成字符串
let obj = {
[Symbol.toPrimitive] (hint) {
switch (hint) {
case 'number':
return 1;
case 'string':
return 's';
case 'default':
return 'default';
default:
return new Error();
}
}
}
console.log(obj * 2); //2
console.log(String(obj)); //s
console.log(obj + 'abc'); //defaultabc
10. Symbol.toStringTag
對象的Symbol.toStringTag屬性,指向一個方法。咱們在獲取一個對象的類型時會調用Object.prototype.toString.call('abc') 返回的是[object String]。這裏,Symbol.toStringTag的返回值就會出如今toString方法的返回字符串中(就是結果中String的部分),看個例子。
class StringTag {
get [Symbol.toStringTag] () {
return 'arr';
}
}
Object.prototype.toString.call(new StringTag()); //"[object arr]"
11. Symbol.unscopables
對象的Symbol.unscopables屬性,指向一個對象。指定了使用with關鍵字時,哪些屬性是被with環境排除的。
舉個例子,Array.prototype[Symbol.unscopables] 默認返回的是
咱們來驗證一下
let a = [1, 2, 3]
with(a){console.log(indexOf)} //ƒ indexOf() { [native code] }
with(a){console.log(findIndex)} //VM15227:1 Uncaught ReferenceError: findIndex is not defined