验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

vscode工具函数Symbol如何使用

阅读:1142 来源:乙速云 作者:代码code

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属性为undefineddone属性为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的使用场景

      SymbolVSCode的应用其实不多,最新的代码只有:

      /**
       * 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...inObject.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作为一种新的数据类型,其功能和用途都比较有限,因此tc39Symbol的基础上提出了一些新的提案,以扩展其功能和用途。其中一个比较重要的提案是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
    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>