重学ES6 | Symbol(不仅仅只是新的数据类型)
前言
这篇文章说实话,在写完的那一刻真的对Symbol这个类型肃然起敬,平时真的不用这个数据类型,也没有想过会用它,之前还是停留在只是知道这个单词的阶段,在写完后才发觉它的强大。
Symbol,ES6 中新增的数据类型,为什么要增加这么一个数据类型?当初一个面试官这么问的我,当时年少轻狂的我,内心的 os 是,我哪知道 🤣!其实还是对 Symbol 这个数据类型不熟悉。
在 ES6 之前,对象的键只能是字符串类型,但是这样有个问题,就是会造成键名命名冲突,后者覆盖前者,这个时候就需要一个唯一值来充当键名,Symbol 横空出世。
1、概念
symbol 是一种基本数据类型,Symbol()函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。但是它不是构造函数,不能用 new Symbol()来创建。
let symbol = Symbol();
typeof symbol; // "symbol"
Symbol 作为对象属性时,当在对象内部时,必须要用方括号括起来,不用方括号括起来代表的是字符串。
let s = Symbol();
let obj = {
[s]: "Jack",
};
obj[s]; // "Jack"
obj.s; // undefined
而且当要取该属性的值时,不能用点运算符,因为点运算符后面同样是字符串类型。
创建 Symbol 数据类型时,都是 Symbol()这么创建的,当打印出来时,都为 Symbol(),这样很难区别各个 Symbol 类型的变量是什么意思。所以在 Symbol 函数内可以接收一个字符串的参数,表示该定义 Symbol 类型变量的描述。
let s1 = Symbol("a");
console.log(s1); // Symbol(a)
s1.toString(); // "Symbol(a)"
如果 Symbol 类型接收的一个对象类型的话,那就会先调用其内部的 toString 方法,将其变为一个字符串,然后才生成一个 Symbol 值。
let arr = [1, 2, 3];
let s1 = Symbol(arr);
console.log(s1); // Symbol(1,2,3)
let obj = {
toString: () => "abc",
};
let s2 = Symbol(obj);
console.log(s2); // Symbol(abc)
Symbol 类型的变量是不能和其他变量参与运算的,而且其只能转为 String 类型和 Boolean 类型。
let s = Symbol();
console.log("1" s); // TypeError: Cannot convert a Symbol value to a string
s.toString(); // "Symbol()"
Boolean(s); // true
Number(s); // TypeError: Cannot convert a Symbol value to a number
2、Symbol.prototype.description
当给 Symbol 添加描述时,可以通过 Symbol.prototype.description 来获取该描述。
let s = Symbol("Jack");
s.description; // 'Jack'
3、Symbol.for(key)和 Symbol.keyFor(sym)
最开始看到这两个方法时,我以为是两个遍历的方法 😅。
- Symbol.for(key):使用给定的 key 搜索现有的 symbol,如果找到则返回该 symbol。否则将使用给定的 key 在全局 symbol 注册表中创建一个新的 symbol。
- Symbol.keyFor(sym):从全局 symbol 注册表中,为给定的 symbol 检索一个 key。
let s1 = Symbol.for("foo");
let s2 = Symbol.for("foo");
s1 === s2; // true
Symbol.for 会搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。所以由其创建的两个相同描述的值会相等。这种创建就和普通的 Symbol()有着截然不同的结果了:
let s1 = Symbol("foo");
let s2 = Symbol("foo");
s1 === s2; // false
因为不管怎样 Symbol()返回的都是一个全新的值,换句话说 Symbol()生成的值没有注册在全局中,所以返回的值都是全新的,而 Symbol.for()会在先在全局中查找,有就返回这个值,没有则创建新的值,但新的值也是挂载在全局中的。
Symbol.keyFor(sym)是在全局中查找是否有该 Symbol 值,有则返回该描述。
let s1 = Symbol.for("Jack");
Symbol.keyFor(s1); // 'Jack'
let s2 = Symbol("Rose");
Symbol.keyFor(s2); // undefined
因为 s2 没有挂载在全局中,所以 Symbol.keyFor()找不到它,故返回 undefined。
4、内置的 Symbol 属性
除了定义自己使用的 Symbol 值以外,ES6 还提供了 13(有可能今后会更多 😛) 个内置的 Symbol 值,指向语言内部使用的方法。
4.1 Symbol.asyncIterator
Symbol.asyncIterator 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于 for await...of 循环。换句话说一个异步可迭代对象内部必须有 Symbol.asyncIterator 属性。
const myAsyncIterable = new Object();
myAsyncIterable[Symbol.asyncIterator] = async function* () {
yield "hello";
yield "async";
yield "iteration!";
};
(async () => {
for await (const x of myAsyncIterable) {
console.log(x);
// expected output:
// "hello"
// "async"
// "iteration!"
}
})();
当执行 for await...of 时,就会执行该变量中 Symbol.asyncIterator 属性值。
4.2、Symbol.hasInstance
Symbol.hasInstance 用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。换句话说当判断一个实例是否为一个类的实例时,其实就是执行该类里面的 Symbol.hasInstance 属性。
class Fu {
[Symbol.hasInstance](num) {
return num === 1;
}
}
1 instanceof new Fu(); // true
2 instanceof new Fu(); // false
4.3、Symbol.isConcatSpreadable
内置的 Symbol.isConcatSpreadable 符号用于配置某对象作为 Array.prototype.concat()方法的参数时是否展开其数组元素。
// 默认情况下
let arr = [1, 2, 3];
let brr = [4, 5, 6];
arr.concat(brr); // [1, 2, 3, 4, 5, 6]
// 设置了Symbol.isConcatSpreadable后
let arr = [1, 2, 3];
let brr = [4, 5, 6];
brr[Symbol.isConcatSpreadable] = false;
arr.concat(brr); // [1, 2, 3, [4, 5, 6]]
将数组的 Symbol.isConcatSpreadable 属性设置为 false 后,使用 concat 方法时该数据就不会展开。
对于类数组而言,默认数组使用 concat 方法该类数组是不展开的,我们可以给类数组的 Symbol.isConcatSpreadable 设置为 true,这样就可以展开了,并且完成了类数组转换为数组,这样类数组转数组又多了一个方法。
// 默认情况下
function foo(x, y) {
let arr = [].concat(arguments);
console.log(arr); //[Arguments(2)]
}
foo(1, 2);
// 设置了Symbol.isConcatSpreadable为true后
function foo(x, y) {
arguments[Symbol.isConcatSpreadable] = true;
let arr = [].concat(arguments);
console.log(arr); //[1, 2]
}
foo(1, 2);
4.4、Symbol.iterator
Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]
对象进行 for...of 循环时,会调用 Symbol.iterator 方法,
4.5、Symbol.match
Symbol.match 指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数。换句话说就是当 str.match()执行时如果该属性存在,就会返回该方法的返回值。
class foo {
[Symbol.match](string) {
return string;
}
}
"Jack".match(new foo()); // 'Jack'
除上述之外,MDN 还提出了该属性另外一个功能:此函数还用于标识对象是否具有正则表达式的行为。比如, String.prototype.startsWith(),String.prototype.endsWith() 和 String.prototype.includes() 这些方法会检查其第一个参数是否是正则表达式,是正则表达式就抛出一个 TypeError。现在,如果 match symbol 设置为 false(或者一个 假值),就表示该对象不打算用作正则表达式对象。
"/bar/".startsWith(/bar/); // TypeError: First argument to String.prototype.startsWith must not be a regular expression
// 当设置为false之后
var re = /foo/;
re[Symbol.match] = false;
"/foo/".startsWith(re); // true
"/baz/".endsWith(re); // false
4.6、Symbol.matchAll
Symbol.matchAll 返回一个迭代器,该迭代器根据字符串生成正则表达式的匹配项。此函数可以被 String.prototype.matchAll() 方法调用。
"abc".matchAll(/a/);
// 等价于
/a/[Symbol.matchAll]("abc");
4.7、Symbol.replace
Symbol.replace 这个属性指定了当一个字符串替换所匹配字符串时所调用的方法。String.prototype.replace() 方法会调用此方法。
String.prototype.replace(searchValue, replaceValue);
// 等同于
searchValue[Symbol.replace](this, replaceValue);
// 例子
class Replace1 {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return `s/${string}/${this.value}/g`;
}
}
console.log("foo".replace(new Replace1("bar"))); // "s/foo/bar/g"
4.8、Symbol.search
Symbol.search 指定了一个搜索方法,这个方法接受用户输入的正则表达式,返回该正则表达式在字符串中匹配到的下标,这个方法由以下的方法来调用 String.prototype.search()。
String.prototype.search(regexp);
// 等价于
regexp[Symbol.search](this);
// 例子
class Search1 {
[Symbol.search](str) {
return `${str} Word`;
}
}
"Hello".search(new Search1()); // Hello Word
4.9、Symbol.species
Symbol.species 是个函数值属性,其被构造函数用以创建派生对象,换句话说 species 访问器属性允许子类覆盖对象的默认构造函数。
我们举个例子:
// 默认情况下
class MyArray extends Array {}
let arr = new MyArray(1, 2, 3);
let brr = arr.map((item) => item);
brr instanceof MyArray; // true
brr instanceof Array; // true
类 MyArray 继承于 Array,arr 为 MyArray 的实例,brr 为 arr 的衍生物,所以 brr 是 MyArray 的实例,并且由于原型链的缘故,brr 也是 Array 的实例。如果此时,我们只想让 brr 为 Array 的实例,那 Symbol.species 属性值就派上用场了。
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
let arr = new MyArray(1, 2, 3);
let brr = arr.map((item) => item);
brr instanceof MyArray; // false
brr instanceof Array; // true
// 默认情况下
class MyArray extends Array {
static get [Symbol.species]() {
return this;
}
}
值得注意的是,定义 Symbol.species 属性时,前面必须声明是静态的 static 并且要运用 get 取值器。
4.10、Symbol.split
Symbol.split 指向 一个正则表达式的索引处分割字符串的方法。 这个方法通过 String.prototype.split() 调用。
String.prototype.split(separator, limit);
// 等价于
separator[Symbol.split](this, limit);
// 例子
class Split1 {
[Symbol.split](str) {
return `${str} Word`;
}
}
"Hello".split(new Split1()); // Hello Word
4.11、Symbol.toPrimitive
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。该函数在调用时,会传递一个字符串参数 hint,表示要转换到的原始值的预期类型。字符串 hint 的类型有三种:'number', 'string', 'default'。
let obj =
{
[Symbol.toPrimitive](hint) {
switch (hint) {
case "number":
return 123;
case "string":
return "123";
case "default":
return "default";
default:
throw new Error();
}
},
} obj; // 123
`${obj}`; // '123'
obj ""; // "default"
4.12、Symbol.toStringTag
Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。通俗点讲就是在 Object.prototype.toString()去判断自定义对象的数据类型时,返回的都是 object,可以通过这个属性来给自定义对象添加类型标签。 在我之前写的【重学 JS 之路】js 基础类型和引用类型写到最精确判断数据类型的方法就是 Object.prototype.toString(),至于是为什么,在这就不过多阐述了,可以看这篇文章。
Object.prototype.toString.call('123'); // [object String]
...more
另外一些对象类型则不然,toString() 方法能识别它们是因为引擎为它们设置好了 toStringTag 标签:
Object.prototype.toString.call(new Map()); // "[object Map]"
Object.prototype.toString.call(function* () {}); // "[object GeneratorFunction]"
Object.prototype.toString.call(Promise.resolve()); // "[object Promise]"
...more
当我们自己定义一个类时,调用 Object.prototype.toString()时,由于没有内部定义 toStringTag 标签,所以只能返回"[object Object]"
class Foo {}
Object.prototype.toString.call(new Foo()); // "[object Object]"
// 设置Symbol.toStringTag
class Foo {
get [Symbol.toStringTag]() {
return "Foo";
}
}
Object.prototype.toString.call(new Foo()); // "[object Foo]"
4.13、Symbol.unscopabless
Symbol.unscopables 指用于指定对象值,其对象自身和继承的从关联对象的 with 环境绑定中排除的属性名称。说白了其属性就是控制,在 with 词法环境中哪些属性会被 with 删除。
Array.prototype[Symbol.unscopabless];
// {
// copyWithin: true,
// entries: true,
// fill: true,
// find: true,
// findIndex: true,
// includes: true,
// keys: true
// }
这里简单的讲解一下 with 函数,with 主要是用来对对象取值的,举个简单的例子:
let obj = {};
with (obj) {
let newa = a;
let newb = b;
console.log(newa newb);
}
// 等价于
let newa = obj.a;
let newb = obj.b;
console.log(newa newb);
with 的 优点: 当 with 传入的值非常复杂时,即当 object 为非常复杂的嵌套结构时,with 就使得代码显得非常简洁。 with 的缺点: js 的编译器会检测 with 块中的变量是否属于 with 传入的对象, 上述例子为例,js 会检测 a 和 b 是否属于 obj 对象,这样就会的导致 with 语句的执行速度大大下降,性能比较差。
回归正题,我们举个例子看一下 Symbol.unscopables 属性的作用。
let obj = {
foo() {
return 1;
}
}
with(obj) {
foo(); // 1
}
// 设置了Symbol.unscopables
let obj = {
foo() {
return 1;
},
get [Symbol.unscopables]() {
return {
foo: true
}
}
}
with(obj) {
foo(); // Uncaught ReferenceError: foo is not defined
}
设置后报错的原因是因为with已经将obj中的foo方法删除了。
此次也是对Symbol有了个重新的认识,也希望对你有所帮助。 点个赞吧!💥🧡💖
参考
后语
- 带你重学ES6 | var、let和const的区别
- 带你重学ES6 | Promsie
- 带你重学ES6 | Generator
- 带你重学ES6 | Async和Await
- 带你重学ES6 | Set和Map
- 带你重学ES6 | Symbol(不仅仅只是一个新的数据类型)
- 带你重学ES6 | Exprort(谨记输出的都是变量)
- 带你重学ES6 | proxy和defineProperty
觉得还可以的,麻烦走的时候能给点个赞,大家一起学习和探讨!
还可以关注我的博客希望能给我的github上点个Start,小伙伴们一定会发现一个问题,我的所有用户名几乎都与番茄有关,因为我真的很喜欢吃番茄❤️!!!
想跟车不迷路的小伙还希望可以关注公众号 前端老番茄 或者扫一扫下面的二维码👇👇👇。
我是一个编程界的小学生,您的鼓励是我不断前进的动力,😄希望能一起加油前进。
本文使用 mdnice 排版
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfjjgib
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
excel图片置于文字下方的方法
PHP中文网 06-27 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
微信提示登录环境异常是什么意思原因
PHP中文网 04-09 -
微信运动停用后别人还能看到步数吗
PHP中文网 07-22 -
微信人名旁边有个图标有什么用
PHP中文网 03-11