# Symbol

ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。Symbol 函数不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。

意思是全局标记,Symbol对象元素的保护作用

IE浏览器不支持Symbol

let obj={name:'jspang',skill:'web'};
let age=Symbol();
obj[age]=18;
obj.sex="man"
for (let item in obj){
	console.log(obj[item]);
	//jspang
	//web
	//man
} 
obj[age]=30;
console.log(obj)//{name: "jspang", skill: "web", sex: "man", Symbol(): 30}
console.log(obj.age)//undefined
console.log(obj[age])//30
let sy = Symbol("KK");
console.log(sy);   // Symbol(KK)
typeof(sy);        // "symbol"
 
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk"); 
sy === sy1;       // false

用途:作为唯一属性名

let sy = Symbol("key1");
 
// 写法1
let syObject1 = {};
syObject1[sy] = "kk";
console.log(syObject1);    // {Symbol(key1): "kk"}
 
// 写法2
let syObject2 = {
  [sy]: "kk"
};
console.log(syObject2);    // {Symbol(key1): "kk"}
 
// 写法3
let syObject3 = {};
Object.defineProperty(syObject3, sy, {value: "kk"});
console.log(syObject3);   // {Symbol(key1): "kk"}

symbol

Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。

Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

# 不加new的创建

// BigInt需要tsconifg的target在ES2020以上
// const symbol = new Symbol()   // TypeError
// const bigint = new BigInt()   // TypeError

const number = new Number()   // OK
const boolean = new Boolean() // OK
const string = new String()   // OK

const date1 =Date()
const date2 =new Date()
console.log(typeof date1)//string
console.log(typeof date2)//object

从 ES6 开始围绕原始数据类型创建一个显式包装器对象已不再被支持,但因历史遗留原因, new Boolean()、new String() 以及 new Number()等 仍可被创建,不过如果没加new,创建的内容的类型不是object,而是具体的string/boolean/number等类型

# Symbol作为属性

let s1 = Symbol('foo'),
    s2 = Symbol('bar');


let o = {
  [s1]: 'foo val',
  [s2]: 'bar val',
  baz: 'baz val',
  qux: 'qux val'
};


Object.defineProperty(o,'foo',{
    value: "fooooo",
    //是否为枚举属性
    enumerable: false
})

console.log(Object.getOwnPropertySymbols(o));
// [Symbol(foo), Symbol(bar)]

console.log(Object.getOwnPropertyNames(o));
// ['baz', 'qux', 'foo']

console.log(Object.getOwnPropertyDescriptors(o));
//{baz: {…}, qux: {…}, foo: {…}, Symbol(foo): {…}, Symbol(bar): {…}}

console.log(Reflect.ownKeys(o));
// ['baz', 'qux', 'foo', Symbol(foo), Symbol(bar)]

# Symbol.isConcatSpreadable

可控制数组打平情况 链接

# Symbol.iterator

es6实现迭代器的默认属性,字符串,数组,集合,映射,arguments对象,Nodelist等dom集合类型都实现了。

该方法返回对象的默认的迭代器,有for...of使用。

let arr=[1,2,3]
let iter =arr[Symbol.iterator]()
console.log(iter)
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
// Array Iterator {}__proto__: Array Iterator
// {value: 1, done: false}
// {value: 2, done: false}
// {value: 3, done: false}
// {value: undefined, done: true}

这个由Symbol.iterator函数生成的对象应该通过next()方法陆续返回值,也可以隐式通过生成器函数返回:

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

# 提前终止迭代器 Symbol.iterator

可选的return方法用于指定在迭代器提前关闭时执行的逻辑。内置语言结构在发现还有很多值可以迭代,但不会消费这些值时,会自动调用return方法

	class Counter {
	  constructor(limit) {
	    this.limit = limit;
	  }
	
	  [Symbol.iterator]() {
	    let count = 1,
	        limit = this.limit;
	    return {
	      next() {
	        if (count <= limit) {
	          return { done: false, value: count++ };
	        } else {
	          return { done: true };
	        }
	      },
	      return() {
	        console.log('Exiting early');
	        return { done: true };
	      }
	    };
	  }
	}
	
	let counter1 = new Counter(5);
	
	for (let i of counter1) {
	  if (i > 2) {
	    break; 
	  }
	  console.log(i);
	}
	// 1
	// 2
	// Exiting early
	
	
	let counter2 = new Counter(5);
	
	try {
	  for (let i of counter2) {
	    if (i > 2) {
	      throw 'err'; 
	    }
	    console.log(i);
	  }
	} catch(e) {}
	// 1
	// 2
	// Exiting early

如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代,数组的迭代器就是不能关闭的!,当然,可以加上return方法,并且会执行,但是依然下次执行迭代,还会继续从未完成的地方继续迭代

let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
iter.return=function(){
	console.log("end")
	return {done:true}
}
let iter1 = a[Symbol.iterator]();

for (let i of iter) {
  console.log(i);
  if (i > 2) {
    break
  }
}
// 1
// 2
// 3
//end

for (let i of iter1) {
  console.log(i+"--");
}
// 1--
// 2--
// 3--
// 4--
// 5--


for (let i of iter) {
  console.log(i);
}
// 4
// 5 

# Symbol.asyncIterator

Symbol.asyncIterator 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环,会自动执行async *[Symbol.asyncIterator]函数

class Emitter {
  constructor(max) {
    this.max = max;
    this.asyncIdx = 0;
  }

  async *[Symbol.asyncIterator]() {
    while(this.asyncIdx < this.max) {
      yield new Promise((resolve) => resolve(this.asyncIdx++));
    }
  }
}

async function asyncCount() {
  let emitter = new Emitter(5);

  for await(const x of emitter) {
    console.log(x);
  }
}

asyncCount();
// 0
// 1
// 2
// 3
// 4

# Symbol.hasInstance

Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此可以用它自定义 instanceof 操作符在某个类上的行为。

在es6中,instanceof会使用Symbol.hasInstance函数来确定关系。默认以Symbol.hasInstance为键的函数会执行同样的操作,只不过操作数对调一下

function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f));  // true

可以手动去定义这个方法返回的值

class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
console.log([] instanceof MyArray); // true
class Bar{
	static [Symbol.hasInstance](){
		return false
	}
}

let bar = new Bar()
console.log(bar instanceof Bar)
console.log(Bar[Symbol.hasInstance](bar))

# Symbol.isConcatSpreadable

内置的Symbol.isConcatSpreadable符号用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。

const alpha = ['a', 'b', 'c'];
const numeric = [1, 2, 3];
let alphaNumeric = alpha.concat(numeric);
//默认情况下,Array.prototype.concat() 展开其元素连接到结果中:
console.log(alphaNumeric);
// expected output: Array ["a", "b", "c", 1, 2, 3]

//设置Symbol.isConcatSpreadable为false:
numeric[Symbol.isConcatSpreadable] = false;
alphaNumeric = alpha.concat(numeric);
console.log(alphaNumeric);
// expected output: Array ["a", "b", "c", Array [1, 2, 3]]

# Array-like 对象使用Symbol.isConcatSpreadable

对于类数组 (array-like)对象,默认不展开。期望展开其元素用于连接,需要设置 Symbol.isConcatSpreadable 为true

var x = [1, 2, 3];
var fakeArray = {
  length: 2,
  0: "hello",
  1: "world"
}
fakeArray[Symbol.isConcatSpreadable]= true
console.log(x.concat(fakeArray))
// [1, 2, 3, "hello", "world"]
  • 其他不是类数组对象,也不是数组,在Symbol.isConcatSpreadable为true时,会直接忽略
var x = [1, 2, 3];

var fakeArray = {
  0: "hello",
  1: "world"
}
fakeArray[Symbol.isConcatSpreadable]= true
console.log(x.concat(fakeArray))
// [1, 2, 3]
var x = [1, 2, 3];
var fakeArray = {
  0: "hello",
  1: "world"
}
console.log(x.concat(fakeArray))
//  [1, 2, 3, {…}]

# Symbol.match

Symbol.match 指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数。

let hasLengthOf10 = {
    [Symbol.match]: function (value) {
        return value.length === 10 ? [value] : null
    }
};
console.log('abcdefghij'.match(hasLengthOf10)); // ['abcdefghij']
console.log('abcdefghi'.match(hasLengthOf10)); // null

# 禁止表达式检查

"/bar/".startsWith(/bar/);

// Throws TypeError, 因为 /bar/ 是一个正则表达式
// 且 Symbol.match 没有修改。

但是,如果将 Symbol.match 置为 false,使用 match 属性的表达式检查会认为该象不是正则表达式对象。startsWith 和 endsWith 方法将不会抛出 TypeError。

var re = /foo/;
re[Symbol.match] = false;
console.log('/foo/oooo'.startsWith(re)); // true

# Symbol.matchAll

属性返回一个迭代器,该迭代器返回与字符串匹配的正则表达式。

const re = /[0-9]+/g;
const str = '2016-01-02|2019-03-07';
const result = re[Symbol.match](str);
console.log(result)
console.log(Array.from(result, x => x[0]));
const result1 = re[Symbol.matchAll](str);
console.log(result1)
console.log(Array.from(result1, x => x[0]));

// ['2016', '01', '02', '2019', '03', '07']
// ['2', '0', '0', '2', '0', '0']
// RegExpStringIterator {}[[Prototype]]: RegExp String Iterator
// ['2016', '01', '02', '2019', '03', '07']
class caseInsensitiveSearch {
  constructor(value) {
    this.value = value.toLowerCase();
  }
  [Symbol.search](string) {
    return string.toLowerCase().indexOf(this.value);
	// return 1000
  }
}

console.log('foobar'.search(new caseInsensitiveSearch('BaR')));
// expected output: 3

# Symbol.species

Symbol.species 是个函数值属性,其被构造函数用以创建派生对象。这个符号作为一个对象的属性,其值是一个函数,该函数作为创建派生对象的构造函数。

说的更简单一点,就是该对象通过某个方法创建子对象时,会调用该属性所对应的方法去获取子对象的构造函数。

class MyArray extends Array {
  // 覆盖 species 到父级的 Array 构造函数上
  static get [Symbol.species]() { return Array}
}
var a = new MyArray(1,2,3)
var mapped = a.map(x => x * x)

console.log(mapped instanceof MyArray) // false
console.log(mapped instanceof Array) // true
console.log(a instanceof MyArray)  // true
console.log(a instanceof Array)   // true
Array.prototype.map = function (callback) {
	var Species = this.constructor[Symbol.species];
	var returnValue = new Species(this.length);
	this.forEach(function (item, index, array) {
		returnValue[index] = callback(item, index, array);
	});
	return returnValue;
}

# Symbol.for() 全局符合注册表[必须用字符串键创建]

ES6会在内部维护一张全局符号注册表。

Symbol.for() 类似单例模式 ,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。

let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1;      // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2;     // true

# Symbol.keyFor()

Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。

let yellow1 = Symbol.for("Yellow");
console.log(Symbol.keyFor(yellow1))//Yellow
let t1 =Symbol('t1')
console.log(Symbol.keyFor(t1))//undefined
let y
console.log(Symbol.keyFor(y))
//demo.html:18 Uncaught TypeError: undefined is not a symbolat Function.keyFor 

# Symbol.split

支持浏览器暂时不多,暂不详细记录

# Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

在 Symbol.toPrimitive 属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个。

// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
var obj1 = {};
console.log(+obj1);     // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"

// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
var obj2 = {
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "hello";
    }
    return true;
  }
};
console.log(+obj2);     // 10      -- hint 参数值是 "number"
console.log(`${obj2}`); // "hello" -- hint 参数值是 "string"
console.log(obj2 + ""); // "true"  -- hint 参数值是 "default"

# Symbol.toStringTag

通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。

Object.prototype.toString.call('foo');     // "[object String]"
Object.prototype.toString.call([1, 2]);    // "[object Array]"
Object.prototype.toString.call(new Map());       // "[object Map]"
Object.prototype.toString.call(function* () {}); // "[object GeneratorFunction]"
Object.prototype.toString.call(Promise.resolve()); // "[object Promise]"
// ... and more

class ValidatorClass {}
 // "[object Object]"
console.log(Object.prototype.toString.call(new ValidatorClass()))
  • 自定义返回的StringTag
class ValidatorClass {
  get [Symbol.toStringTag]() {
    return "Validator";
  }
}
console.log(Object.prototype.toString.call(new ValidatorClass()))
 // "[object Validator]"

# symbol的应用场景

  • 给对象取一个属性,不会和别的属性冲突
  • 当一个对象有较多属性时(往往分布在不同文件中由模块组合而成),很容易将某个属性名覆盖掉,使用 Symbol 值可以避免这一现象,比如 vue-router 中的 name 属性。
// a.js 文件
export const aRouter = {
  path: '/index',
  name: Symbol('index'),
  component: Index
},

// b.js 文件

export const bRouter = {
  path: '/home',
  name: Symbol('index'), // 不重复
  component: Home
},

// routes.js 文件
import { aRouter } from './a.js'
import { bRouter } from './b.js'

const routes = [
  aRouter,
  bRouter
]
  • 模拟类的私有方法
const permission = Symbol("permission");

class Auth {
  [permission]() {
    console.log("do something");
  }
  constructor() {
    console.log("constructor");
    this[permission]();
  }
}

let auth = new Auth();
console.log(auth);
//constructor
//do something
//Auth{}

这种情况通过类的实例是无法取到该方法,模拟类的私有方法。

TypeScript可以使用 private 关键字实现,所以这种方法可以在js中使用。

  • Symbol.prototype.description:Symbol([description]) 中可选的字符串即为这个 Symbol 的描述,如果想要获取这个描述
const sym: symbol = Symbol('imooc')

console.log(sym);               // Symbol(imooc)
console.log(sym.toString());    // Symbol(imooc)
console.log(sym.description);   // imooc

TIPS: description 属性是 ES2019 的新标准,Node.js 最低支持版本 11.0.0。

# symbol的类型转换

symbol不同于其他类型,它无法与数字或字符串进行运算,也无法显示转化成数字。

这些操作都会得到报错

var sum = Symbol('sum');
Number(sum);//x
1+sum;//x 
''+sum;//x

不过,symbol可以转化为字符串,并且可以转化为布尔值。

Boolean(sum);//trye
!sum;//false
String(sum);//Symbol(sum)
sum.toString();//Symbol(sum)

# symbol的内置符号

内置符号:ES6 提供了一些内置符号,也叫作知名符号(Well-Known Symbol)。它们暴露了语言的内部逻辑,允许开发人员修改或拓展规范所描述的对象特征或行为。每一个内置符号对应 Symbol 对象的一个属性,如 Symbol.hasInstance、Symbol.iterator 等

let digit = {
 [Symbol.hasInstance](number) {
 return !(number % 2); //判断数字是否为偶数
 }
};
1 instanceof digit; //false
2 instanceof digit; //true
let arr1 = [3, 4];
[1, 2].concat(arr1); //[1, 2, 3, 4]
let arr2 = [3, 4];
arr2[Symbol.isConcatSpreadable] = false; //禁止展开
[1, 2].concat(arr2); //[1, 2, [3, 4]]
let regex = {
 [Symbol.match](str) {
 return str.substr(0, 10);
 }
 },
 message = "My name is strick";
message.match(regex); //"My name is"
let people = {
 [Symbol.toStringTag]: "People"
};
people.toString(); //"[object People]"

这些特性在框架和库的设计中尤其有用,因为它们允许开发者以更细粒度的方式控制类型检查和模式匹配。在使用这些特性时,开发者应该始终注意保持代码的清晰和可维护性,避免过度使用或滥用这些特性,从而引入不必要的复杂性或潜在的错误。

最后更新: 11/21/2024, 2:37:03 PM