ES6 核心知识点:Symbol 用法与内置 Symbol 全解析

0 阅读5分钟

一、Symbol -- ES6新增的类型

(一)、定义

它是基础类型,不是类,不可实例化;

let s = Symbol('创建')

Symbol()函数每次调用会创建一个新的唯一值

console.log(Symbol() === Symbol())
// false

Symbol() 函数接受一个可选参数作为描述,这样使Symbol更具有语义性。

下面创建两个symbol分别为: firstName and lastName

let firstName = Symbol('first name'),
    lastName = Symbol('last name');
console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)
console.log(firstName.toString()); // Symbol(first name)
console.log(lastName.toString()); // Symbol(last name)
console.log(firstName.description); // first name
console.log(lastName.description); // last name

console.log()打印symbol的时候会隐式调用symboltoString()方法;里头的值是description

(二)、共享 Symbol

要创建一个共享的symbol,要使用Symbol.for()函数,而不是Symbol()

Symbol.for() 也接受一个可选参数作为描述

let a = Symbol.for('aaa');
let b = Symbol.for('aaa');
console.log(b === a); // true
console.log(Symbol.keyFor(a));
console.log(Symbol.keyFor(b));
  • Symbol.for()不会像Symbol每次重新创建一个symbol,它会首先在全局中查找是否有已经创建的'aaa'的symbol,如果有就会返回已经创建的symbol,如果没有就会创建一个新的symbol
  • Symbol.for()创建的变量通过Symbol.keyFor()方法获取值;
  • Symbol()创建的变量通过Symbol.decription方法获取值;

二、作用

(一)、使用Symbol作唯一值

let statuses = {
    OPEN: Symbol('已下单'),
    IN_PROGRESS: Symbol('配送中'),
    COMPLETED: Symbol('订单完成'),
    CANCELED: Symbol('订单取消')
};

(二)、 使用symbol作为对象属性

使用Symbol作为属性名称

let user1 = {
    name: '张三'
    key: Symbol()
}
let user2 = {
    name: '张三'
    key: Symbol()
}
let userJson = {
    [user1.key]: {grade: 23},
    [user2.key]: {grade: 26}
}
console.log(userJson);

一般定义一样的值的对象,会指向同一个地址,所以下面的user2会覆盖user1; 但是可以通过Symbol可以指向两个地址;

三、symbol内置函数

(一)、Symbol.hasInstance

Symbol.hasInstance 是一个改变instanceof操作符默认行为的symbol

instanceof的使用:

obj instanceof type;
// 判断引用数据的类型,通过原型链一直往上找,找到的话返回true,否则为false

那么js就会执行Symbol.hasInstance方法;它会调用typeSymbol.hasInstance静态方法,将obj作为参数;

class Stack {
}
console.log([] instanceof Stack);
// false

[] 数组不是Stack类所创建的实例,所以返回false。

假设要使[] 数组是Stack类所创建的实例,返回true,我们可以重写Symbol.hasInstance的方法

上面例子中,Stack本应该不是数组,但是可以通过重写Symbol.hasInstance修改instanceof的返回值;

(二)、 Symbol.iterator

Symbol.iterator 指定函数是否会返回对象的迭代器。具有 Symbol.iterator 属性的对象称为可迭代对象。在ES6中,Array、Set、Map和string都是可迭代对象。

ES6提供了for...of循环,它可以用在可迭代对象上。

var numbers = [1, 2, 3];
for (let num of numbers) {
    console.log(num);
}

// 1
// 2
// 3

在背后,JavaScript引擎首先调用numbers数组的 Symbol.iterator 方法来获取迭代器对象,然后它调用 iterator.next() 方法并将迭代器对象的value属性复制到num变量中,3次迭代后,对象的done属性为true,循环退出。

可以通过Symbol.iterator来获取数组的迭代器对象;

var iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}

通过给自定义的类自定义Symbol.iterator 让其可以被循环取值

class List {
   constructor() {
     this.elements = [];
   }
   
   add(element) {
     this.elements.push(element);
     return this;
   }
   
   *[Symbol.iterator]() {
     for (let element of this.elements) {
       yield element;
     }
   }
}
let chars = new List();

chars.add('A')
     .add('B')
     .add('C');
     
// 使用Symbol.iterator实现了迭代
for (let c of chars) {
  console.log(c);
}

(三)、 Symbol.isConcatSpreadable

数组中可以通过concat()方法合并多个数组;也可以使用concat()来传入单个元素,而非数组;

let odd  = [1, 3],
    even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]

let extras = all.concat(5);
console.log(extras); // [1, 3, 2, 4, 5]

除了数组别的引用类型或自定义的对象都不支持concat()方法;

要在concat()时将list对象中的元素单独添加到数组中的,我们需要将Symbol.isConcatSpreadable属性添加到list对象中,像下面这样:

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2,
    [Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]

如果将Symbol.isConcatSpreadable设置为trueconcat()就会将list整个对象合并到数组中。

(四)、 Symbol.toPrimitive

对象的Symbol.toPrimitive属性。指向一个方法。该对象被转化为原始类型的值时,会调用这个办法,返回该对象对应的原始类型值。 Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一个有三种模式。

  • Number: 该场合需要转成数值
  • String: 该场合需要转成字符串
  • Default: 该场合可以转成数值,也可以转成字符串。

Symbol.toPrimitive在类型转换方面,优先级是最高的

const test = { 
	i: 10, 
	toString: function() {
	   console.log('toString');
	  return this.i; 
	}, 
	valueOf: function() { 
	   console.log('valueOf');
	   return this.i; 
	},
    [Symbol.toPrimitive](hint) {
        if(hint === 'number'){
          console.log('Number场景');
          return 123;
        }
        if(hint === 'string'){
          console.log('String场景');
          return 'str';
        }
        if(hint === 'default'){
          console.log('Default 场景');
          return 'default';
        }
    }
}

console.log(test);          // { i:10, toString: f, valueOf: f, Symbol(Symbol.toPrimitive): f }
console.log(+test);         // 123 Number场景
console.log(''+test);       // default Default 场景
console.log(String(test));  // str String场景
console.log(Number(test));  // 123 Number场景
console.log(test == '10');  // false default场景
console.log(test === '10'); // false

上面代码中、+test中的加号命名为一元加号+test本质就是转成数值的意思;

(五)、其他内置函数

  • Symbol.match(regex):一个在调用 String.prototype.match() 方法时调用的方法,用于比较字符串。
  • Symbol.replace(regex, replacement):一个在调用 String.prototype.replace() 方法时调用的方法,用于替换字符串的子串。
  • Symbol.search(regex):一个在调用 String.prototype.search() 方法时调用的方法,用于在字符串中定位子串。
  • Symbol.species(regex):用于创建派生对象的构造函数。
  • Symbol.split:一个在调用 String.prototype.split() 方法时调用的方法,用于分割字符串。
  • Symbol.toStringTag:一个在调用 String.prototype.toString() 方法时使用的字符串,用于创建对象描述。
  • Symbol.unscopables:一个定义了一些不可被 with 语句引用的对象属性名称的对象集合。

感谢您抽出宝贵的时间观看本文;本文是JavaScript 系列的第 1 篇,后续会持续更新ES, Dom, 队列等等内容,欢迎关注~