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