vscode工具函数Symbol如何使用

工具使用   发布日期:2024年05月15日   浏览次数:504

这篇文章主要介绍“vscode工具函数Symbol如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“vscode工具函数Symbol如何使用”文章能帮助大家解决问题。

    什么是Symbol?

    符号(

    Symbol
    )是JavaScript中的一个原始数据类型,是ECMAScript 6标准引入的新特性。符号是一种类似于字符串的数据类型,但与字符串不同的是,符号是唯一的并且不可变的。

    Symbol
    的定义方法如下:
    const mySymbol = Symbol('my symbol');

    每次调用

    Symbol
    创建的值都是唯一的,即使对同一个参数调用两遍
    Symbol
    它们的值还是不一样的:
    Symbol("foo") === Symbol("foo"); // false

    js的第六种基本数据类型

    Symbol
    出现之前,
    Javascript
    已经有五种内置的基本数据类型:
    • 布尔值(

      Boolean
      ):表示真或假,只有两个取值:true和false。
    • 数字(

      Number
      ):表示整数或浮点数,可以使用十进制、十六进制、八进制、科学计数法等多种表示方式。
    • 字符串(

      String
      ):表示文本字符串,可以使用单引号、双引号、反引号等方式表示。
    • 空值(

      Null
      ):表示一个空值或不存在的对象。
    • 未定义(

      Undefined
      ):表示一个未定义的值或未声明的变量。

    Symbol
    则作为第六种基本数据类型加入到语言中:
    • 符号(

      Symbol
      ):表示唯一的、不可变的值,用于保护属性名、实现私有属性或方法等场景。

    Symbol的起源

    在JavaScript诞生之初,对象属性只能使用字符串作为键,这导致了一些问题。例如,当两个不同的对象试图使用相同的字符串作为属性名时,可能会导致属性名冲突。此外,

    JavaScript
    中没有一种简单的方法来实现私有属性或方法。

    其实对于

    Symbol
    的追溯早在
    Lisp
    语言中就有体现:
    (setq x (intern "my-symbol"))

    这里其实就是创建了一个名为

    my-symbol
    的符号对象,并将其赋值给变量
    x

    另外,ES6引入

    Symbol
    其实离不开
    Ruby
    的身影,在
    Ruby
    中,可以使用冒号(
    :
    )来创建符号。冒号后面跟着符号的名称,如:
    :my_symbol

    可以看到其实

    Ruby
    的语法更加简洁,定义和使用都是用冒号区分:
    person = {
      'name' => 'John',
      'age' => 30,
      :gender => 'Male'
    }
    puts person[:gender]  # 输出:'Male'

    所以,在这样的需求背景下,

    ES6
    在首批特性中包含了
    Symbol
    也不足为奇了。

    Symbol的基本知识

    定义与使用

    JavaScript
    中,可以使用
    Symbol()
    函数来创建一个符号,如下所示:
    const mySymbol = Symbol();

    Symbol
    函数可以接受一个描述性字符串作为参数,用于标识符号的含义,如下所示:
    const mySymbol = Symbol('my symbol');

    需要注意的是,每个

    Symbol()
    函数调用都会返回一个唯一的符号,即使描述性字符串相同,它们也是不同的符号。

    Symbol
    类型的值可以用作对象的属性名,如下所示:
    const mySymbol = Symbol('my symbol');
    const myObject = {
      [mySymbol]: 'hello'
    };
    console.log(myObject[mySymbol]);  // 输出:'hello'

    在上面的代码中,我们使用符号

    mySymbol
    作为对象
    myObject
    的属性名,并将其值设置为
    'hello'
    。使用符号作为属性名的好处是它们不会与其他属性名冲突,并且对外不可见,因此可以用于实现私有属性或方法等场景。

    另外,JavaScript中的Symbol类型有两个特殊的方法

    Symbol.for()
    Symbol.keyFor()
    ,用于创建全局符号和获取已经存在的全局符号。
    • Symbol.for()
      : 用于创建或获取一个全局符号,如果全局符号已经存在,则返回已经存在的符号,否则创建一个新的全局符号。例如:
    const mySymbol = Symbol.for('my symbol');
    const sameSymbol = Symbol.for('my symbol');
    console.log(mySymbol === sameSymbol);  // 输出:true

    在上面的代码中,我们使用

    Symbol.for()
    方法来创建一个全局符号
    'my symbol'
    ,并将其赋值给
    mySymbol
    变量。然后,我们再次使用
    Symbol.for()
    方法来获取同一个全局符号,赋值给
    sameSymbol
    变量。由于全局符号已经存在,因此
    sameSymbol
    变量的值等于
    mySymbol
    变量的值,输出
    true

    Symbol的重要属性

    1. Symbol.iterator: 用于指定对象的默认迭代器,例如:

    const myObject = {
      *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
      }
    };
    for (const value of myObject) {
      console.log(value);
    }
    // 输出:1 2 3

    在上面的代码中,我们为

    myObject
    对象设置了
    Symbol.iterator
    符号,并指定了一个生成器函数作为迭代器的实现。然后,我们可以使用
    for...of
    循环迭代
    myObject
    对象,并输出其中的值。

    2. Symbol.hasInstance: 用于定义一个对象是否为某个构造函数的实例。

    Symbol.hasInstance
    方法接受一个参数,表示要检查的对象。该方法需要返回一个布尔值,表示该对象是否为该构造函数的实例。例如:
    class MyClass {
      static [Symbol.hasInstance](obj) {
        return obj instanceof Array;
      }
    }
    console.log([] instanceof MyClass);  // 输出:true
    console.log({} instanceof MyClass);  // 输出:false

    在上面的代码中,我们定义了一个

    MyClass
    类,并使用
    Symbol.hasInstance
    方法自定义了
    instanceof
    运算符的行为,使其检查对象是否为数组。当检查[]对象时,
    instanceof
    运算符返回
    true
    ,因为[]是
    Array
    的实例;当检查
    {}
    对象时,
    instanceof
    运算符返回
    false
    ,因为{}不是
    Array
    的实例。

    需要注意的是,

    Symbol.hasInstance
    方法是一个静态方法,需要定义在构造函数的静态属性中。另外,
    Symbol.hasInstance
    方法不能被继承,因此子类需要重新定义该方法。

    3. Symbol.toStringTag: 用于自定义对象的默认字符串描述。

    当调用

    Object.prototype.toString()
    方法时,会使用该对象的
    Symbol.toStringTag
    属性作为默认的字符串描述,例如:
    class MyObject {
      get [Symbol.toStringTag]() {
        return 'MyObject';
      }
    }
    const obj = new MyObject();
    console.log(Object.prototype.toString.call(obj));  // 输出:'[object MyObject]'

    在上面的代码中,我们定义了一个

    MyObject
    类,并使用
    Symbol.toStringTag
    属性自定义了该类的默认字符串描述。然后,我们创建了一个
    obj
    对象,并使用
    Object.prototype.toString()
    方法获取其字符串描述,输出
    '[object MyObject]'

    需要注意的是,

    Symbol.toStringTag
    属性只有在调用
    Object.prototype.toString()
    方法时才会生效,对其他方法没有影响。另外,如果没有定义
    Symbol.toStringTag
    属性,则默认使用构造函数的名称作为字符串描述。

    4. Symbol.asyncIterator: 用于指定对象的默认异步迭代器。

    当使用

    for await...of
    循环迭代一个对象时,会调用该对象的
    Symbol.asyncIterator
    方法获取异步迭代器。

    Symbol.asyncIterator
    方法需要返回一个异步迭代器对象,该对象实现了
    next()
    方法,并返回一个
    Promise
    对象。当迭代器迭代到结束时,
    next()
    方法应该返回一个
    Promise
    对象,该
    Promise
    对象的
    value
    属性为
    undefined
    done
    属性为
    true

    例如,下面的代码演示了如何使用Symbol.asyncIterator属性定义一个异步迭代器:

    const myObject = {
      async *[Symbol.asyncIterator]() {
        yield Promise.resolve(1);
        yield Promise.resolve(2);
        yield Promise.resolve(3);
      }
    };
    (async function() {
      for await (const value of myObject) {
        console.log(value);
      }
    })();
    // 输出:1 2 3

    在上面的代码中,我们为

    myObject
    对象设置了
    Symbol.asyncIterator
    符号,并指定了一个异步生成器函数作为异步迭代器的实现。然后,我们使用
    for await...of
    循环迭代
    myObject
    对象,并输出其中的值。

    需要注意的是,使用

    Symbol.asyncIterator
    属性定义的异步迭代器只能使用
    for await...of
    循环进行迭代,不能使用普通的
    for...of
    循环。此外,
    Symbol.asyncIterator
    属性只有在支持异步迭代器的环境中才能使用,例如
    Node.js
    的版本必须在
    10.0.0
    以上才支持异步迭代器。

    Symbol的实现原理

    symbol
    作为基本数据类型实现比较简单,在最新的
    v8
    代码实现如下:
    Symbol Factory::NewSymbolInternal(AllocationType allocation) {
      DCHECK(allocation != AllocationType::kYoung);
      // Statically ensure that it is safe to allocate symbols in paged spaces.
      STATIC_ASSERT(Symbol::kSize <= kMaxRegularHeapObjectSize);
      Symbol symbol = Symbol::cast(AllocateRawWithImmortalMap(
          Symbol::kSize, allocation, read_only_roots().symbol_map()));
      DisallowGarbageCollection no_gc;
      // Generate a random hash value.
      int hash = isolate()->GenerateIdentityHash(Name::kHashBitMask);
      symbol.set_raw_hash_field(Name::kIsNotIntegerIndexMask |
                                (hash << Name::kHashShift));
      symbol.set_description(read_only_roots().undefined_value(),
                             SKIP_WRITE_BARRIER);
      symbol.set_flags(0);
      DCHECK(!symbol.is_private());
      return symbol;
    }

    该函数使用

    AllocateRawWithImmortalMap()
    方法为新的
    Symbol
    对象分配内存,并将其强制转换为
    Symbol
    类型。接着,该函数使用
    DisallowGarbageCollection
    类禁用垃圾回收器,以确保不会在生成哈希值的过程中触发垃圾回收。接下来,该函数使用
    GenerateIdentityHash()
    方法生成一个随机的哈希值,并将其存储在新的
    Symbol
    对象中。然后,该函数将
    Symbol
    对象的描述设置为
    undefined
    ,并将其标志设置为
    0
    。最后,该函数返回新创建的Symbol对象。

    所以使用

    hash
    来唯一标识一个
    symbol
    ,在
    v8
    内部还实现了
    symbol-table
    来实现
    Symbol.for
    的查找,本质上也是一个哈希表。

    为了简单起见,我们用

    js
    来模拟一下
    Symbol
    的实现:
    const registry = {};
    function createSymbol(description) {
      const symbol = Object.create(null);
      symbol.toString = () => `Symbol(${description || ''})`;
      Object.defineProperty(symbol, 'description', {
        value: description,
        writable: false,
        configurable: false,
        enumerable: false,
      });
      return symbol;
    }
    function Symbol(description) {
      if (typeof description !== 'undefined') {
        description = String(description);
      }
      if (registry[description]) {
        return registry[description];
      }
      const symbol = createSymbol(description);
      registry[description] = symbol;
      return symbol;
    }
    Symbol.for = function (key) {
      if (registry[key]) {
        return registry[key];
      }
      const symbol = createSymbol(key);
      registry[key] = symbol;
      return symbol;
    };
    Symbol.keyFor = function (symbol) {
      for (const key in registry) {
        if (registry.hasOwnProperty(key) && registry[key] === symbol) {
          return key;
        }
      }
    };
    export default Symbol;

    我们使用一个全局对象

    registry
    来存储
    Symbol
    对象及其描述符信息。
    createSymbol()
    函数用于创建新的
    Symbol
    对象,其中使用了
    Object.create()
    方法来创建一个没有原型的对象,并通过定义
    toString()
    description
    属性来实现
    Symbol
    对象的基本功能。
    Symbol()
    函数用于创建新的
    Symbol
    对象,它根据传入的描述符信息从
    registry
    中查找
    Symbol
    对象,如果找到了则返回已有的
    Symbol
    对象,否则创建新的
    Symbol
    对象并添加到
    registry
    中。

    Symbol的使用场景

    Symbol
    VSCode
    的应用其实不多,最新的代码只有:
    /**
     * Can be passed into the Delayed to defer using a microtask
     * */
    export const MicrotaskDelay = Symbol('MicrotaskDelay');

    在实际中,

    Symbol
    经常被用于:

    1. 唯一属性键:Symbol可以作为对象属性的键,避免属性名冲突。

    这在创建第三方库或插件时非常有用,因为可以确保库或插件的属性不会与其他代码意外冲突。

    const uniqueKey = Symbol('uniqueKey');
    const obj = {
      [uniqueKey]: 'This value is uniquely keyed'
    };

    2. 定义私有属性(当然这一点现在ES规范已经有更好的方式了)

    使用Symbol可以在对象上创建"私有"属性,它们不会被常规的属性枚举(如

    for...in
    Object.keys()
    JSON.stringify()
    )包含在内。这有助于保护对象内部实现细节。

    3. 内置Symbol

    JavaScript内置了一些具有特定功能的

    Symbol
    。例如,
    Symbol.iterator
    可以定义对象的迭代行为,
    Symbol.toStringTag
    可以自定义
    Object.prototype.toString.call()
    方法的输出。

    4. 注册全局Symbol

    Symbol.for()
    方法允许在全局
    Symbol
    注册表中创建或获取
    Symbol
    。这对于跨多个地方或模块使用相同的
    Symbol
    时非常有用。
    const globalSymbol = Symbol.for('globalSymbol');
    const sameGlobalSymbol = Symbol.for('globalSymbol');
    console.log(globalSymbol === sameGlobalSymbol); // true

    Symbol的发展

    tc39
    上已经有两个关于
    Symbol
    的提案:

    Symbols as WeakMap keys(Stage3)

    Symbol
    作为一种新的数据类型,其功能和用途都比较有限,因此
    tc39
    Symbol
    的基础上提出了一些新的提案,以扩展其功能和用途。其中一个比较重要的提案是
    Symbols as WeakMap keys
    ,该提案已经进入到
    Stage3
    阶段。

    WeakMap
    是一种新的集合类型,可以用于存储对象和关联的元数据。
    WeakMap
    的特点是键必须是对象,值可以是任意类型。
    WeakMap
    的另一个特点是,当键对象不再被引用时,
    WeakMap
    会自动删除该键值对,以避免内存泄漏。

    Symbols as WeakMap keys
    提案的目的是将
    Symbol
    作为
    WeakMap
    的键。这样,就可以在不影响
    WeakMap
    的自动垃圾回收机制的情况下,将
    Symbol
    作为对象的元数据来使用。
    const weak = new WeakMap();
    // Pun not intended: being a symbol makes it become a more symbolic key
    const key = Symbol('my ref');
    const someObject = { /* data data data */ };
    weak.set(key, someObject);

    Symbol Predicates Proposal(Stage2)

    这是另一个关于Symbol的提案,添加了以下判断方法:

    Symbol.isRegistered(symbol)
    Symbol.isWellKnown(symbol)

    其实对于库作者而言,了解更多关于

    Symbol
    的信息是很重要的。根据使用情况,了解一个
    Symbol
    是否真正唯一、可伪造(已注册)或跨域共享(众所周知)可能非常关键。例如,将
    Symbol
    用作
    WeakMap
    键需要确保
    Symbol
    未被注册。该提案处于第二阶段,正在受到
    JavaScript
    社区的广泛关注。如果被采纳,它将为
    Symbol
    的应用带来更多的灵活性。
    function isWeakMapKey(key) {
      switch (typeof key) {
        case "object":
          return key !== null;
        case "function":
          return true;
        case "symbol":
          return !Symbol.isRegistered(sym);
      }
      return false;
    }
    isWeakMapKey({}); // true
    isWeakMapKey(Symbol()); // true
    isWeakMapKey("foo"); // false
    isWeakMapKey(Symbol.for("foo")); // false
    isWeakMapKey(Symbol.asyncIterator); // true

    您还可以检查是否获得了真正唯一的

    Symbol
    const isUniqueSymbol = sym => typeof sym === "symbol" && !(Symbol.isRegistered(sym) || Symbol.isWellKnown(sym));
    isUniqueSymbol(Symbol()); // true
    isUniqueSymbol(Symbol.for("foo")); // false
    isUniqueSymbol(Symbol.asyncIterator); // false
    isUniqueSymbol({}); // false

    以上就是vscode工具函数Symbol如何使用的详细内容,更多关于vscode工具函数Symbol如何使用的资料请关注九品源码其它相关文章!