ES6 中的 Symbol —— 多的是你不知道的事

简介: ES6 中的 Symbol —— 多的是你不知道的事

image.png


一、Symbol 类型

Symbol 是 ES6 中新增的基本数据类型,它属于原始值,代表唯一的、不可变. 【目的】是保证对象属性的唯一性,也是为了解决属性冲突.

二、基本用法

  • 使用 Symbol() 函数初始化,由于它是属于原始类型,所以 typeof 会直接返回 symbol
let sym = Symbol();
console.log(typeof sym); // symbol
复制代码
  • 初始化可传参,如 Symbol('hello') 那么 hello 就会作为这个 symbol 的描述,为了方便未来调试程序时有更直观的展示
let sym1 = Symbol();
console.log(sym1); // Symbol()
let sym2 = Symbol('this is sym2');
console.log(sym2); // Symbol(this is sym2)
复制代码
  • 通过 Symbol() 初始化同样参数的变量,也不会相等,可以理解为调用一次 Symbol() 就得到一个新的标识,不论参数是否相同
let sym1 = Symbol();
let sym2 = Symbol();
console.log(sym1 == sym2); // false
let sym3 = Symbol('foo');
let sym4 = Symbol('foo');
console.log(sym3 == sym4); // false
复制代码
  • Symbol()函数不能与 new 关键字一起作为构造函数使用,目的是为了避免和 Boolean、String 或 Number 的实例那样,存在包装对象
// 1. 实例化例子
let myBoolean = new Boolean();
console.log(typeof myBoolean); // "object"
let myString = new String();
console.log(typeof myString); // "object"
let myNumber = new Number();
console.log(typeof myNumber); // "object"
let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor
// 2. 包装对象例子
let myBoolean = new Boolean();
let myString = new String();
let myNumber = new Number();
let mySymbol = Symbol();
myBoolean.add = ()=>{};
myString.add = ()=>{};
myNumber.add = ()=>{};
mySymbol.add = ()=>{};
console.log(myBoolean.add);// ()=>{}
console.log(myString.add);// ()=>{}
console.log(myNumber.add);// ()=>{}
console.log(mySymbol.add);// undefind
// 3. Symbol 转包装对象
let mySymbol = Object(Symbol());
mySymbol.add = ()=>{};
console.log(mySymbol.add);// ()=>{}
复制代码
  • 全局符号注册表
  • Symbol() 每初始化一次,就会生成一个新 symbol
  • Symbol.for() 初始化时可以传递字符串参数,并检查注册表是否存在对应的 symbol,如果没有则直接存储在注册表中,下一次在调用 Symbol.for() 并且传参一致时,直接返回注册表中存在的 symbol.
let sym1 = Symbol.for('foo'); // 创建新符号
let sym2 = Symbol.for('foo'); // 重用已有符号 
console.log(sym1 === sym2); // true
console.log(Symbol('foo') === Symbol('foo')); // false
console.log(Symbol('foo') === Symbol.for('foo')); // false
复制代码
  • Symbol.keyFor() 查询注册表,参数为全局 symbol,即用 Symbol.for() 创建得到的 symbol
// 创建全局符号
let sym1 = Symbol.for('foo');
console.log(Symbol.keyFor(s1)); // foo
// 创建普通符号
let sym2 = Symbol('bar');
console.log(Symbol.keyFor(sym2)); // undefined
复制代码

三、使用 Symbol 作为属性正常用于对象的【属性】

// 1. symbol 定义
      let s1 = Symbol('1'),
        s2 = Symbol('2'),
        s3 = Symbol('3'),
        s4 = Symbol('4'),
        s5 = Symbol('5');
    // 2. 多种赋值方式,和普通对象属性一致
      let o = {
        [s1]: 's1',
        num: 1,
        str: 'hello'
      }
      o[s2] = 's2'
      Object.defineProperty(o, s3, { value: 's3' });
      Object.defineProperties(o, {
        [s4]: { value: 's4' },
        [s5]: { value: 's5' },
      });
      console.log(o); // {num: 1, str: 'hello', Symbol(1): 's1', Symbol(2): 's2', Symbol(3): 's3', Symbol(4): 's4', Symbol(5): 's5',}
      // 3. 获取 key 的方式不一致,getOwnPropertyNames 和 getOwnPropertySymbols 方法是互斥的
      console.log(Object.getOwnPropertyNames(o)); // ['num', 'str']
      console.log(Object.getOwnPropertySymbols(o)); // [Symbol(1), Symbol(2), Symbol(3), Symbol(4), Symbol(5)]
     // 4. 获取所有的对象自身上的属性(普通属性 + symbol属性)
      console.log(Object.getOwnPropertyDescriptors(o)); // {num: {…}, str: {…}, Symbol(1): {…}, Symbol(2): {…}, Symbol(3): {…}, …}
      console.log(Reflect.ownKeys(o)); // ['num', 'str', Symbol(1), Symbol(2), Symbol(3), Symbol(4), Symbol(5)]
复制代码

四、常用内置符号

ECMAScript 6 引入了一批常用内置符号(well-known symbol)

  • 目的是用于暴露语言内部的行为,开发者可以直接访问、重写或模拟这些行为.
  • 内置符号都以 Symbol 工厂函数字符串属性的形式存在【例如:Symbol.iterator】.
  • 所有内置符号属性都是不可写、不可枚举、不可配置的.

1、与遍历有关(Symbol.asyncIterator & Symbol.Iterator)

  • Symbol.asyncIterator 表示实现异步迭代器 API 的函数,可 for-await-of 循环会利用这个函数执行异步迭代操作
  • PS:ES2018 规范中定义的,只有在最新版的浏览器才支持
class Emitter {
     constructor(max) {
       this.max = max;
       this.asyncIndex = 0;
     }
     async *[Symbol.asyncIterator]() {
         while (this.asyncIndex < this.max) {
            yield new Promise((resolve) => resolve(this.asyncIndex++));
        }
     }
 }
  let emitter = new Emitter(5);
  for await (const x of emitter) {
     console.log(x);
  }
复制代码
  • Symbol.Iterator 表示实现迭代器 API 的函数,可用 for-of 循环这个函数执行迭代操作
  • 能够使用 for-of 遍历的对象,要么自身实现了或者原型上存在键名为 [Symbol.iterator] 的函数,并且这个函数默认会返回一个对象,这个对象带有 next 函数的实现,比如 Generator 函数.
// 1. 未实现 [Symbol.iterator]
  let obj = {};
  // 抛出错误: Uncaught TypeError: obj is not iterable
  for(const x of obj) {
     console.log(x);
  }
  // 2. 实现 [Symbol.iterator]
   let obj = {
      [Symbol.iterator]: function* () {
          let i = 0
          while (i < 5) {
            yield i++;
          }
        }
      }
      for (const x of obj) {
        console.log(x); // 0 1 2 3 4
      }
复制代码

2. 与 instanceof 相关(Symblo.hasInstance | Symbol.species)

  • instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型.
  • Symblo.hasInstance 作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例,由 instanceof 操作符使用.
// 1. 通过 instanceof 判断
   class Foo {}
   let f = new Foo();
   console.log(f instanceof Foo) // true
   // 2. 通过 [Symbol.hasInstance] 的判断
   class Foo {}
   let f = new Foo();
   console.log(Foo[Symbol.hasInstance]) // ƒ [Symbol.hasInstance]() { [native code] }
   console.log(Foo[Symbol.hasInstance](f)) // true
   // 3. 自定义 instanceof 的返回值
    class Foo {
       static [Symbol.hasInstance] = function () {
         return false;
       }
    }
    let f = new Foo();
    console.log(f instanceof Foo); // false
复制代码
  • Symbol.species 作为一个属性表示一个函数值,该函数作为创建派生对象的构造函数.
class Arr1 extends Array {}
      class Arr2 extends Array {
        static get [Symbol.species]() {
          return Array
        }
      }
      let arr1 = new Arr1();
      arr1 = arr1.concat('arr111'); 
      console.log(arr1 instanceof Arr1); // true
      console.log(arr1 instanceof Array); // true
      let arr2 = new Arr2();
      arr2 = arr2.concat('arr222');
      console.log(arr2 instanceof Arr2); // false
      console.log(arr2 instanceof Array); // true
复制代码

3. 与数组有关(Symbol.isConcatSpreadable)

  • Symbol.isConcatSpreadable 表示一个布尔值,如果是 true,则意味着对象应 该用 Array.prototype.concat() 打平其(数组 || 伪数组)的元素.
// 1. 正常使用 Array.prototype.concat
     let arr1 = [1,2];
     let arr2 = [3,4];
     // 直接访问得到 undefinded ,但合并的结果是被拍平的,因此,可以认为默认情况下需要被拍平
     console.log(arr2[Symbol.isConcatSpreadable]); // undefinded
     let newArr = arr1.concat(arr2); 
     console.log(newArr);// [1, 2, 3, 4]
     // 2. 修改不需要被拍平的元素
     let arr1 = [1,2];
     let arr2 = [3,4];
     console.log(arr2[Symbol.isConcatSpreadable]); // undefined
     arr2[Symbol.isConcatSpreadable] = false;
     console.log(arr2[Symbol.isConcatSpreadable]); // false
     let newArr = arr1.concat(arr2);
     console.log(newArr); // [1, 2, Array(2)]
     // 3. 伪数组也具备这个属性
     let likeObj = { length: 2, 0: 'name', 1: 'age' }; 
     let  arr = [];
     console.log(likeObj[Symbol.isConcatSpreadable]); // undefined 
     console.log(arr.concat(likeObj)); // [{ length: 1, 0: 'name', 1: 'age' }] 
     likeObj[Symbol.isConcatSpreadable] = true; 
     console.log(arr.concat(likeObj)); // ['name', 'age']
复制代码

4. 与字符串相关 Symbol.[match | replace | search | split]

PS:以上这些方法,在正则表达式 RegExp.protype 上都有默认实现,因此字符串的大多数 api 都和 RegExp 对象一起使用,默认情况下,即便传递的不是 RegExp 类型,也会被强制转换为 RegExp 类型去使用.

  • Symbol.match 作为一个属性表示一个正则表达式方法,该方法用正则表达式 去匹配字符串。由 String.prototype.match()方法使用
  • string.match(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型,如:'11'.macth( {num:11} ) ==> '11'.macth(new RegExp( {num:11} ))
  • string.match(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.match 方法
// 1. string.match(RegExp)
console.log('hello'.match(/llo/)); // ["llo", index: 2, input: "hello", groups: undefined]
// 2. string.match(非 RegExp 类型)
var obj = {};
var result = 'hello'.match(obj);// 这里相当于 'hello'.match( new RegExp(obj) ) 
console.log(result); // ["e", index: 1, input: "hello", groups: undefined]
// 3. 自定义 string.match 的匹配规则
var obj = {
  [Symbol.match]: (target) => { // target 是 foobar
    let index = target.indexOf('oo');
    return {
      value: target.slice(index,3),
      index
    };
  }
};
console.log('foobar'.match(obj)); // {value: "oo", index: 1}
复制代码
  • Symbol.replace 作为一个属性表示一个正则表达式方法,该方法替换一个字符 串中匹配的子串。由 String.prototype.replace()方法使用
  • string.replace(exp, str),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
  • string.replace(exp, str) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.replace 方法
// 1. Symbol.replace(exp, str)
var target = 'hello';
var newStr = target.replace(/o/,'66');
console.log(newStr); // hell66
// 2. Symbol.replace(非 RegExp 类型, str)
var target = 'hello';
var newStr = target.replace(['o'],'77'); // 相当于 target.replace(new RegExp(['o']),'77')
console.log(newStr); // hell77
// 3. 实现自定义 Symbol.replace 替换规则
var target = 'hello';
var obj = {
  [Symbol.replace]: function(target){
    var index = target.indexOf('e');
    return 'haha' + target.slice(index);
  }
};
var newStr = target.replace(obj,'66');
console.log(newStr); // hahaello
复制代码
  • Symbol.search 作为一个属性表示一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 String.prototype.search()方法使用
  • string.search(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
  • string.search(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.search 方法
// 1. string.search(exp)
var target = 'hello';
var index = target.search('ll');
console.log(index); // 2
// 2. string.search(非 RegExp 类型)
var target = 'hello';
var obj =  {};
var index = target.search(obj); // target.search(new RegExp(obj))
console.log(index); // 1
// 3. 实现自定义 string.search 查找规则
var target = 'hello';
var obj = {
  [Symbol.search]: function(target){
    var index = target.indexOf('ll');
    return index;
  }
};
var index = target.search(obj);
console.log(index); // 2
复制代码
  • Symblo.split 作为一个属性表示一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 String.prototype.split()方法使用.
  • string.split(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
  • string.split(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.split 方法
// 1. string.split(RegExp)
var target = 'hello-world';
var arr = target.split('-');
console.log(arr); // ["hello", "world"]
// 2. string.split(非 RegExp 类型)
var target = 'hello-world';
var arr = target.split(['-']);// 相当于 target.split(new RegExp(['-']))
console.log(arr); // ["hello", "world"]
// 3. 实现自定义 string.split 拆分规则
var target = 'hello-world';
var obj = {
  [Symbol.split]: function(target){
    var length = target.length;
    var arr = [];
    for(var index = 0; index < length; index++){
      arr[index] = target[index];
    }
    return arr;
  }
};
var arr = target.split(obj);
console.log(arr); // ["h", "e", "l", "l", "o", "-", "w", "o", "r", "l", "d"]
复制代码

5. 与复杂类型相关 Symbol.[toPrimitive | toStringTag | unscopables]

  • Symbol.toPrimitive表示一个方法,该方法将对象转换为相应的原始值. 由 ToPrimitive 抽象操作使用
// 1. 复杂类型 转 基本类型
var obj = {};
 // 1.1 obj 转 string
console.log(obj + ' & hello'); // [object Object] & hello
 // 1.2 obj 转 number
console.log(obj - 1); // NaN
 // 1.3 obj 转 boolean
console.log(!!obj); // true
// 2. 实现自定义的转换规则
var obj = {
  [Symbol.toPrimitive]: function (type) {
    switch (type) {
      case 'string':
        return '666';
      case 'number':
        return 889;
      case 'default':
      default:
        return 0;
    }
  }
};
// 2.1 obj 转 string
console.log(String(obj)); // 666 & hello
// 2.2 obj 转 number
console.log(obj - 1); // 888
// 2.3 obj 转 boolean
console.log(Boolean(obj)); // true
复制代码
  • Symbol.toStringTag 表示一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 Object.prototype.toString()使用
// 1. Object.prototype.toString() 用于返回数据类型
var arr = [1],
  obj = { name: 'zs' },
  func = function getName() { 
    console.log(obj.name); 
  };
// 1.1 调用 Object.prototype.toString()
console.log(obj.toString()); // [object Object]
// 1.2 这两个调用的都不是 Object.prototype.toString 方法
  // 1.2.1 Array.prototype.toString()
console.log(arr.toString()); // 1
  // 1.2.2 Function.prototype.toString()
console.log(func.toString()); // function getName() { console.log(obj.name); }
// 3. 通过 Symbol.toStringTag 更改 Object 类型
obj[Symbol.toStringTag] = 'Array';
console.log(obj.toString()); // [object Array]
// typeof 返回类型不会受影响
console.log(typeof obj); // object
复制代码
  • Symbol.unscopables 表示一个对象,该对象所有的以及继承的属性,都会从关联对象的 with 环境绑定中排除
  • 不推荐使用 with,因此也不推荐使用 Symbol.unscopables
// 1. 正常可见
let o = { foo: 'bar' };
with (o) {
  console.log(foo); // bar
}
// 2. 设置不可见
o[Symbol.unscopables] = {
   foo: true
};
with (o) {
  console.log(foo); // ReferenceError
}
复制代码

EDN


目录
相关文章
|
2月前
|
JavaScript 前端开发
ES6:什么是Symbol?
ES6:什么是Symbol?
27 1
|
2月前
ES6之Symbol
ES6之Symbol
|
2月前
|
JavaScript 前端开发
|
2月前
|
JavaScript 前端开发 开发者
|
2月前
|
存储 JavaScript
ES6+新特性-Symbol与Set/Map数据结构
ES6 引入了三种新的数据结构:Symbol、Set和Map。Symbol是唯一且不可变的值,常用于定义对象的独特属性;Set存储不重复值,适合数组去重;Map则是键值对集合,键可为任意类型,提供了更灵活的存储方式。这些新数据结构提供了更高效的操作手段,分别解决了属性命名冲突、数据去重和复杂键值对存储的问题。示例展示了如何使用Symbol、Set和Map进行基本操作。
|
2月前
|
JavaScript
js开发:请解释什么是ES6的Symbol,以及它的用途。
ES6的Symbol数据类型创建唯一值,常用于对象属性键(防冲突)和私有属性。示例展示了如何创建及使用Symbol:即使描述相同,两个Symbol也不等;作为对象属性如`obj[symbol1] = &#39;value1&#39;`;也可作枚举值,如`Color.RED = Symbol(&#39;red&#39;)`。
|
2月前
|
JavaScript 前端开发
ES6之原始数据类型Symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它属于 JavaScript 语言的原生数据类型之一,其他数据类型是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。Symbol的作用是创建一个不可变且唯一的标识符,可以用作对象属性的键。它可以用来解决属性名冲突的问题,避免命名冲突。
45 0
|
10月前
ES6: Symbol概念与用法举例
ES6: Symbol概念与用法举例
31 0
|
11月前
|
JavaScript
es6数据类型Symbol以及es6操作数组常用的方法
es6数据类型Symbol以及es6操作数组常用的方法
70 0