為什麼會有Symbol型別?怎麼使用?

2022-03-22 13:00:39
什麼是 Symbol?為什麼會有這麼個東西?下面本篇文章給大家介紹一下JavaScript中的Symbol型別,聊聊使用方法,希望對大家有所幫助!

什麼是 Symbol?為什麼會有這麼個東西?

Symbol(符號)是 ES6 新增的資料型別。Symbol 是原始值(基礎資料型別),且 Symbol 範例是唯一、不可變的。它的產生是因為要用來唯一的標記,進而用作非字串形式的物件屬性,是確保物件屬性使用唯一識別符號,不會發生屬性衝突的危險。【相關推薦:】

用法

1. 基本用法

符號需要使用 Symbol()函數初始化。因為符號本身是原始型別,所以 typeof 操作符對符號返回 symbol。

let sym = Symbol();
console.log(typeof sym); // symbol

Symbol()函數可以接收一個字串引數用來描述,後,後續可以通過這個字串來偵錯程式碼。但值得注意的是,多個 Symbol()函數即使接受的引數是一樣的,他們的值也是不相等的。

let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
let fooSymbol = Symbol("foo");
let otherFooSymbol = Symbol("foo");

console.log(genericSymbol == otherGenericSymbol); // false
console.log(fooSymbol == otherFooSymbol); // false

2. 使用全域性符號登入檔

如果在程式碼中有多個地方需要使用同一個 Symbol 範例的時候,可以傳入一個字串,然後使用 Symbol.for()方法來建立一個可以複用的 Symbol,類似於單例模式,在第一次使用 Symbol.for()的時候,它會根據傳入的引數會全域性的去尋找是否使用 Symbol.for()建立過同樣的範例,如果有,則複用,如果沒有,則新建

let fooGlobalSymbol = Symbol.for("foo"); // 建立新符號
let otherFooGlobalSymbol = Symbol.for("foo"); // 重用已有符號
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

Symbol.for()建立的範例和 Symbol()建立的範例區別: Symbol()建立的範例永遠都是唯一的,不會因為你傳入的引數相同而跟其他的範例相等,但是 Symbol.for()建立的範例如果引數相同的話他們是會相等的,因為他們會公用同一個 Symbol 範例

let fooSymbol = Symbol("foo");
let otherFooSymbol = Symbol("foo");
console.log(fooSymbol == otherFooSymbol); // false

let fooGlobalSymbol = Symbol.for("foo"); // 建立新符號
let otherFooGlobalSymbol = Symbol.for("foo"); // 重用已有符號
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

3. 使用符號作為屬性

物件中的屬性一般都是字串的形式,但其實也是可以使用 Symbol 範例來作為屬性的,這樣的好處就是你新增的屬性不會覆蓋掉以前的任何屬性

let s1 = Symbol("foo"),
  s2 = Symbol("bar"),
  s3 = Symbol("baz"),
  s4 = Symbol("qux");
let o = {
  [s1]: "foo val",
};
// 這樣也可以:o[s1] = 'foo val';
console.log(o);
// {Symbol(foo): foo val}
Object.defineProperty(o, s2, { value: "bar val" });
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val}
Object.defineProperties(o, {
  [s3]: { value: "baz val" },
  [s4]: { value: "qux val" },
});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val,
// Symbol(baz): baz val, Symbol(qux): qux val}

注意: 建立 Symbol 範例作為物件屬性的時候,如果改 symbol 一開始沒有宣告一個變數進行接收的話,後續就必須遍歷物件的所有符號屬性才能找到相應的屬性鍵:

let o = {
  [Symbol("foo")]: "foo val",
  [Symbol("bar")]: "bar val",
};
console.log(o);
// {Symbol(foo): "foo val", Symbol(bar): "bar val"}
let barSymbol = Object.getOwnPropertySymbols(o).find(symbol => symbol.toString().match(/bar/));
console.log(barSymbol);
// Symbol(bar)

4. 常用內建符號

ES6 也引入了一批常用內建符號(well-known symbol),用於暴露語言內部行為,開發者可以直接存取、重寫或模擬這些行為。如果對這些預設的屬性進行了修改的話,是可以改變一些操作最後執行的結果的。比如 for-of 迴圈會在相關物件上使用 Symbol.iterator 屬性,那麼就可以通過在自定義物件上重新定義 Symbol.iterator 的值,來改變 for-of 在迭代該物件時的行為。

5. Symbol.asyncIterator

其實就是一個返回 Promise 的 Generator,一般配合 for await of 使用

6. Symbol.hasInstance

根據 ECMAScript 規範,這個符號作為一個屬性表示「一個方法,該方法返回物件預設的 AsyncIterator。 由 for-await-of 語句使用」。換句話說,這個符號表示實現非同步迭代器 API 的函數。

這個屬性定義在 Function 的原型上。都知道 instanceof 操作符可以用來確定一個物件範例是否屬於某個建構函式。其原理就是 instanceof 操作符會使用 Symbol.hasInstance 函數來確定關係

function Foo() {}
let f = new Foo();
console.log(f instanceof Foo); // true
class Bar {}
let b = new Bar();
console.log(b instanceof Bar); // true

如果你重新定義一個函數的 Symbol.hasInstance 屬性,你就可以讓 instanceof 方法返回一些意料之外的東西

class Bar {}
class Baz extends Bar {
  static [Symbol.hasInstance]() {
    return false;
  }
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false

Symbol.isConcatSpreadabl

這個屬性定義在 Array 的原型上

根據 ECMAScript 規範,這個符號作為一個屬性表示「一個布林值,如果是 true,則意味著物件應該用 Array.prototype.concat()打平其陣列元素」。ES6 中的 Array.prototype.concat()方法會 根據接收到的物件型別選擇如何將一個類陣列(偽陣列)物件拼接成陣列範例。所以修改 Symbol.isConcatSpreadable 的值可以修改這個行為。

Symbol.isConcatSpreadable 對應的效果

false: 將一整個物件新增進陣列true: 將一整個對打平新增進陣列

let initial = ["foo"];
let array = ["bar"];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', Array(1)]
let arrayLikeObject = { length: 1, 0: "baz" };
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(arrayLikeObject)); // ['foo', {...}]

arrayLikeObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLikeObject)); // ['foo', 'baz']
let otherObject = new Set().add("qux");
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(otherObject)); // ['foo', Set(1)]
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // ['foo']

8. Symbol.iterator

根據 ECMAScript 規範,這個符號作為一個屬性表示「一個方法,該方法返回物件預設的迭代器。由 for-of 語句使用」

該屬性會返回一個 Generator 函數,for of 就會依次的去呼叫 next()方法,這就是為什麼 for of 可以使用在某些物件身上。

class Emitter {
  constructor(max) {
    this.max = max;
    this.idx = 0;
  }
  *[Symbol.iterator]() {
    while (this.idx < this.max) {
      yield this.idx++;
    }
  }
}
function count() {
  let emitter = new Emitter(5);
  for (const x of emitter) {
    console.log(x);
  }
}
count();
// 0
// 1
// 2
// 3
// 4

9. Symbol.match

根據 ECMAScript 規範,這個符號作為一個屬性表示「一個正規表示式方法,該方法用正規表示式去匹配字串。由 String.prototype.match()方法使用」。

String.prototype.match()方法會使用以 Symbol.match 為鍵的函數來對正規表示式求值。所以更改一個正規表示式的 Symbol.match 屬性,可以讓 String.prototype.match()得到你想要的值

console.log(RegExp.prototype[Symbol.match]);
// ƒ [Symbol.match]() { [native code] }
console.log("foobar".match(/bar/));
// ["bar", index: 3, input: "foobar", groups: undefined]

class FooMatcher {
  static [Symbol.match](target) {
    return target.includes("foo");
  }
}
console.log("foobar".match(FooMatcher)); // true
console.log("barbaz".match(FooMatcher)); // false
class StringMatcher {
  constructor(str) {
    this.str = str;
  }
  [Symbol.match](target) {
    return target.includes(this.str);
  }
}
console.log("foobar".match(new StringMatcher("foo"))); // true
console.log("barbaz".match(new StringMatcher("qux"))); // false

11. Symbol.search

這個符號作為一個屬性表示「一個正規表示式方法,該方法返回字串中 匹配正規表示式的索引。由 String.prototype.search()方法使用」

12. Symbol.species

這個符號作為一個屬性表示「一個函數值,該函數作為建立派生物件的構 造函數」。

13. Symbol.split

這個符號作為一個屬性表示「一個正規表示式方法,該方法在匹配正則表 達式的索引位置拆分字串。由 String.prototype.split()方法使用」。

14. Symbol.toPrimitive

這個符號作為一個屬性表示「一個方法,該方法將物件轉換為相應的原始 值。由 ToPrimitive 抽象操作使用」

15. Symbol.toStringTag

這個符號作為一個屬性表示「一個字串,該字串用於建立物件的預設 字串描述。由內建方法 Object.prototype.toString()使用」

16. Symbol.unscopables

這個符號作為一個屬性表示「一個物件,該物件所有的以及繼承的屬性, 都會從關聯物件的 with 環境繫結中排除

更多程式設計相關知識,請存取:!!

以上就是為什麼會有Symbol型別?怎麼使用?的詳細內容,更多請關注TW511.COM其它相關文章!