ES6學習筆記3---Symbol

ES6引入Symbol的緣由:防止屬性名的衝突(ES5的對象屬性名都是字符串,容易形成屬性名的衝突)。javascript

ES6引入一種新的原始數據類型:Symbol,表示獨一無二的值。它是javascript語言的第七種數據類型,前六種是:UndefinedNullBooleanStringNumberObjectjava

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...infor...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
}
相關文章
相關標籤/搜索