ES6_Symbol
Symbol基本使用
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型(其余六种数据类型:undefined 、number 、boolean、string、object、function),是一种类似于字符串的数据类型。
Symbol特点
1) Symbol的值是唯一的,用来解决命名冲突的问题
2) Symbol值不能与其他数据进行运算
3) Symbol定义的对象属性不能使用 for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名
<script> //创建Symbol let s = Symbol(); // console.log(s, typeof s); let s2 = Symbol('哈哈'); let s3 = Symbol('哈哈'); //Symbol.for 创建 let s4 = Symbol.for('哈哈'); let s5 = Symbol.for('哈哈'); console.log(s2.toString()); // Symbol(哈哈) console.log(s2.description); // 哈哈 console.log(s4.description); // 哈哈 console.log(Symbol.keyFor(s2)); // undefined console.log(Symbol.keyFor(s4)); // 哈哈 console.log(s2 === s3); // false console.log(s4 === s5); // true // 不能与其他数据进行运算 // let result = s + 100; // let result = s > 100; // let result = s + s; </script>
Symbol创建对象属性
<script> //向对象中添加方法 up down let game = { name: '俄罗斯方块', up: function() {}, down: function() {} }; //声明一个对象 // let methods = { // up: Symbol(), // down: Symbol() // }; // game[methods.up] = function(){ // console.log("我可以改变形状"); // } // game[methods.down] = function(){ // console.log("我可以快速下降!!"); // } // console.log(game); let youxi = { name: "狼人杀", [Symbol('say')]: function() { console.log("我可以发言") }, [Symbol('zibao')]: function() { console.log('我可以自爆'); } } console.log(youxi) </script>
Symbol内置值
除了定义自己使用的Symbol 值以外, ES6 还提供了 11个内置的 Symbol值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
内置值 | 简介 |
Symbol.hasInstance | 当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的Symbol.isConcatSpreadable属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。 |
Symbol.replace | 当该对象被str.replace(myObject)方法调用时,会返回该方法的返回值。 |
Symbol.search | 当该对象被str. search (myObject)方法调用时,会返回该方法的返回值。 |
Symbol.split | 当该对象被str. split (myObject)方法调用时,会返回该方法的返回值。 |
Symbol.iterator | 对象进行for...of循环时,会调用 Symbol.iterator方法,返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol. toStringTag | 在该对象上面调用toString方法时 ,返回该方法的返回值 |
Symbol. unscopables | 该对象指定了使用with关键字时,哪些属性会被 with环境排除。 |
Symbol.hasInstance
class Person{
static [Symbol.hasInstance](param) {
console.log(param);
console.log('我被用来检测类型');
return true;
}
}
let o = {}
console.log(o instanceof Person); // {} '我被用来检测类型' true
Symbol.isConcatSpreadable
ES6中的Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。
覆盖Symbol.isConcatSpreadable的值可以修改这个行为对象的Symbol.isConcatSpreadable属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。
const arr = [1, 2, 3];
const arr2 = [4, 5, 6];
console.log(arr.concat(arr2)); // [1, 2, 3, 4, 5, 6]
console.log(arr2[Symbol.isConcatSpreadable]); // undefined
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr.concat(arr2)); //[1, 2, 3, [4, 5, 6]]
上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable
默认等于undefined
。该属性等于true
时,也有展开的效果。
类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable
属性设为true
,才可以展开。
let obj = {length: 2, 0: 'c', 1: 'd'};
console.log(['a', 'b'].concat(obj, 'e')) // ['a', 'b', obj, 'e']
obj[Symbol.isConcatSpreadable] = true;
console.log(['a', 'b'].concat(obj, 'e')) // ['a', 'b', 'c', 'd', 'e']
Symbol.isConcatSpreadable
属性也可以定义在类里面。
class A1 extends Array {
constructor(args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor(args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}
let a1 = new A1();
a1[0] = 3;
a1[1] = 4;
let a2 = new A2();
a2[0] = 5;
a2[1] = 6;
console.log([1, 2].concat(a1).concat(a2)) // [1, 2, 3, 4, [5, 6]]
上面代码中,类A1
是可展开的,类A2
是不可展开的,所以使用concat
时有不一样的结果。
注意,Symbol.isConcatSpreadable
的位置差异,A1
是定义在实例上,A2
是定义在类本身,效果相同。
Symbol.species
对象的Symbol.species
属性,指向一个构造函数。创建衍生对象时,会使用该属性。
class MyArray extends Array {
}
const a = new MyArray();
console.log(a.map(x => x) instanceof MyArray) // true
上面代码中,子类MyArray
继承了父类Array
。a.map(x => x)
会创建一个MyArray
的衍生对象,该衍生对象还是MyArray
的实例。
现在,MyArray
设置Symbol.species
属性。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
上面代码中,由于定义了Symbol.species
属性,创建衍生对象时就会使用这个属性返回的的函数,作为构造函数。
这个例子也说明,定义Symbol.species
属性要采用get
读取器。
默认的Symbol.species
属性等同于下面的写法。
static get [Symbol.species]() {
return this;
}
现在,再来看前面的例子。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray();
console.log(a.map(x => x) instanceof MyArray) // false
console.log(a.map(x => x) instanceof Array) // true
上面代码中,a.map(x => x)
创建的衍生对象,就不是MyArray
的实例,而直接就是Array
的实例。
再看一个例子。
class T1 extends Promise {
}
class T2 extends Promise {
static get [Symbol.species]() {
return Promise;
}
}
console.log(new T1(r => r()).then(v => v) instanceof T1) // true
console.log(new T2(r => r()).then(v => v) instanceof T2) // false
上面代码中,T2
定义了Symbol.species
属性,T1
没有。结果就导致了创建衍生对象时(then
方法),T1
调用的是自身的构造方法,而T2
调用的是Promise
的构造方法。
总之,Symbol.species
的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。
它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。
Symbol.match
对象的Symbol.match
属性,指向一个函数。当执行str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值。
// String.prototype.match(regexp)
// 等同于
// regexp[Symbol.match](this)
class MyMatcher {
[Symbol.match](string) {
return 'hello world'.indexOf(string);
}
}
console.log('e'.match(new MyMatcher())) // 1
Symbol.replace
对象的Symbol.replace
属性,指向一个方法,当该对象被String.prototype.replace
方法调用时,会返回该方法的返回值。
// String.prototype.replace(searchValue, replaceValue)
// 等同于
// searchValue[Symbol.replace](this, replaceValue)
const x = {};
x[Symbol.replace] = (...s) => console.log(s);
console.log('Hello'.replace(x, 'World')) // ["Hello", "World"]
Symbol.replace
方法会收到两个参数:
第一个参数是replace
方法正在作用的对象,上面例子是Hello
第二个参数是替换后的值,上面例子是World
Symbol.search
对象的Symbol.search
属性,指向一个方法,当该对象被String.prototype.search
方法调用时,会返回该方法的返回值。
// String.prototype.search(regexp)
// 等同于
// regexp[Symbol.search](this)
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
console.log('foobar'.search(new MySearch('foo'))) // 0
Symbol.split
对象的Symbol.split
属性,指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值。
// String.prototype.split(separator, limit)
// 等同于
// separator[Symbol.split](this, limit)
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
let index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [
string.substr(0, index),
string.substr(index + this.value.length)
];
}
}
console.log('foobar'.split(new MySplitter('foo'))) // ['', 'bar']
console.log('foobar'.split(new MySplitter('bar'))) // ['foo', '']
console.log('foobar'.split(new MySplitter('baz'))) // 'foobar'
上面方法使用Symbol.split
方法,重新定义了字符串对象的split
方法的行为
Symbol.iterator
对象的Symbol.iterator
属性,指向该对象的默认遍历器方法。
对象进行for...of
循环时,会调用Symbol.iterator
方法,返回该对象的默认遍历器
//声明一个数组
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
//使用 for...of 遍历数组
// for(let v of xiyou){
// console.log(v);
// }
let iterator = xiyou[Symbol.iterator]();
//调用对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
迭代器自定义遍历对象
<script> //声明一个对象 // const banji = { // name: "终极一班", // stus: [ // 'xiaoming', // 'xiaoning', // 'xiaotian', // 'knight' // ], // [Symbol.iterator]() { // //索引变量 // let index = 0; // // // let _this = this; // return { // next: function() { // if (index < _this.stus.length) { // const result = { // value: _this.stus[index], // done: false // }; // //下标自增 // index++; // //返回结果 // return result; // } else { // return { // value: undefined, // done: true // }; // } // } // }; // } // } //遍历这个对象 // for (let v of banji) { // console.log(v); // } // for (let v in banji) { // console.log(v); // } //简洁版 let obj = { name: "Ges", age: 21, hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"], *[Symbol.iterator]() { for (let arg of Object.values(this)) { yield arg; } } } for (let item of obj) { console.log(item) } </script>
Symbol.toPrimitive
对象的Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive
被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
- Number:该场合需要转成数值
- String:该场合需要转成字符串
- Default:该场合可以转成数值,也可以转成字符串
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
console.log(2 * obj) // 246
console.log(3 + obj) // '3default'
console.log(obj == 'default') // true
console.log(String(obj)) // 'str'
Symbol. toStringTag
对象的Symbol.toStringTag
属性,指向一个方法。
在该对象上面调用Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型。
也就是说,这个属性可以用来定制[object Object]
或[object Array]
中object
后面的那个字符串。
// 例一
console.log({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]"
// 例二
class Collection {
get [Symbol.toStringTag]() {
return 'xxx';
}
}
let x = new Collection();
console.log(Object.prototype.toString.call(x)) // "[object xxx]"
ES6 新增内置对象的Symbol.toStringTag
属性值如下。
JSON[Symbol.toStringTag]
:'JSON'Math[Symbol.toStringTag]
:'Math'- Module 对象
M[Symbol.toStringTag]
:'Module' ArrayBuffer.prototype[Symbol.toStringTag]
:'ArrayBuffer'DataView.prototype[Symbol.toStringTag]
:'DataView'Map.prototype[Symbol.toStringTag]
:'Map'Promise.prototype[Symbol.toStringTag]
:'Promise'Set.prototype[Symbol.toStringTag]
:'Set'%TypedArray%.prototype[Symbol.toStringTag]
:'Uint8Array'等WeakMap.prototype[Symbol.toStringTag]
:'WeakMap'WeakSet.prototype[Symbol.toStringTag]
:'WeakSet'%MapIteratorPrototype%[Symbol.toStringTag]
:'Map Iterator'%SetIteratorPrototype%[Symbol.toStringTag]
:'Set Iterator'%StringIteratorPrototype%[Symbol.toStringTag]
:'String Iterator'Symbol.prototype[Symbol.toStringTag]
:'Symbol'Generator.prototype[Symbol.toStringTag]
:'Generator'GeneratorFunction.prototype[Symbol.toStringTag]
:'GeneratorFunction'
Symbol. unscopables
对象的Symbol.unscopables
属性,指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除。
console.log(Array.prototype[Symbol.unscopables])
// {
// copyWithin: true,
// entries: true,
// fill: true,
// find: true,
// findIndex: true,
// includes: true,
// keys: true
// }
console.log(Object.keys(Array.prototype[Symbol.unscopables]))
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
上面代码说明,数组有 7 个属性,会被with
命令排除。
// 没有 unscopables 时
class MyClass {
foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1
}
// 有 unscopables 时
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2
}
上面代码通过指定Symbol.unscopables
属性,使得with
语法块不会在当前作用域寻找foo
属性,即foo
将指向外层作用域的变量。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现