-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ES6 系列之模拟实现 Symbol 类型 #87
Comments
@mqyqingfeng 没想到没有人提问啊😀,请教下博主关于 |
说到Symbol的使用场景。单例模式(判断函数是已创建,已创建就返回它,没有就创建)里对Symbol的应用简直太经典了。 |
例子中明明可以用数组,强行来了一波Symbol。 |
典型的应用场景是在class里面实现真正的内置原型方法 |
请问 |
@daoket 那个例子当中使用数组的语义化没有 map 表现的更好呀。 |
大佬你好,小弟现在对 Symbol的 魔术字符串 的 使用 还有一些疑惑,疑惑的重点是 如何与 后端的接口数据进行交互,以文章中的代码为例
resource.type 如果是 接口返回的数据,那么应该赋予什么值才能与 TYPE_IMAGE 这样的常量进行匹配呢?当不用Symbol的时候,还有一个字符串来对应,现在这个值 接口是不知道的,该如何处理? |
@mqyqingfeng |
我觉得特性十并没有实现 var s1 = SymbolPolyfill('foo')
var s2 = Symbol('foo')
var obj = {
name: 'test',
[s1]: 's1_symbol',
[s2]: 's2_symbol'
}
console.log(Object.keys(obj))
// ["name", "@@foo_1"]
|
补充下用途定义类的私有变量/方法User.js const AGE = Symbol()
const GET_AGE = Symbol()
class User {
constructor(name, sex, age) {
this.name = name
this.sex = sex
this[AGE] = age
this[GET_AGE] = function () {
return this[AGE]
}
}
printAge() {
console.log(this[GET_AGE]())
}
}
module.exports = User test.js let User = require('./User')
let u1 = new User('xm', 'M', 18)
let u2 = new User('xh', 'W', 20)
console.log(u1.name) // xm
console.log(u1.age) // undefined
u1.printAge() // 18
console.log(u2.name) // xh
console.log(u2.age) // undefined
u2.printAge() // 20 运用在单例模式中Phone.js class Phone {
constructor() {
this.name = '小米'
this.price = '1999'
}
}
let key = Symbol.for('Phone')
if (!global[key]) {
global[key] = new Phone()
}
module.exports = global[key] test.js let p1 = require('./Phone')
let p2 = require('./Phone')
console.log(p1 === p2) // true |
这是什么单例? require有cache, 本来就不会再执行一次phone.js |
@Dengyy Symbol的这两个属性也是 |
|
corejs中的polyfill实现应该是完整的吧 |
前言
实际上,Symbol 的很多特性都无法模拟实现……所以先让我们回顾下有哪些特性,然后挑点能实现的……当然在看的过程中,你也可以思考这个特性是否能实现,如果可以实现,该如何实现。
回顾
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。
1. Symbol 值通过 Symbol 函数生成,使用 typeof,结果为 "symbol"
2. Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。
3. instanceof 的结果为 false
4. Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
5. 如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。
6. Symbol 函数的参数只是表示对当前 Symbol 值的描述,相同参数的 Symbol 函数的返回值是不相等的。
7. Symbol 值不能与其他类型的值进行运算,会报错。
8. Symbol 值可以显式转为字符串。
9. Symbol 值可以作为标识符,用于对象的属性名,可以保证不会出现同名的属性。
10. Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。
11. 如果我们希望使用同一个 Symbol 值,可以使用 Symbol.for。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
12. Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。
分析
看完以上的特性,你觉得哪些特性是可以模拟实现的呢?
如果我们要模拟实现一个 Symbol 的话,基本的思路就是构建一个 Symbol 函数,然后直接返回一个独一无二的值。
不过在此之前,我们先看看规范中调用 Symbol 时到底做了哪些工作:
当调用 Symbol 的时候,会采用以下步骤:
考虑到还需要定义一个 [[Description]] 属性,如果直接返回一个基本类型的值,是无法做到这一点的,所以我们最终还是返回一个对象。
第一版
参照着规范,其实我们已经可以开始写起来了:
只是参照着规范,我们已经实现了特性的第 2、5、6 点。
第二版
我们来看看其他的特性该如何实现:
1. 使用 typeof,结果为 "symbol"。
利用 ES5,我们并不能修改 typeof 操作符的结果,所以这个无法实现。
3. instanceof 的结果为 false
因为不是通过 new 的方式实现的,所以 instanceof 的结果自然是 false。
4. Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。主要是为了在控制台显示,或者转为字符串时,比较容易区分。
当我们打印一个原生 Symbol 值的时候:
可是我们模拟实现的时候返回的却是一个对象,所以这个也是无法实现的,当然你修改 console.log 这个方法是另讲。
8. Symbol 值可以显式转为字符串。
当调用 String 方法的时候,如果该对象有 toString 方法,就会调用该 toString 方法,所以我们只要给返回的对象添加一个 toString 方法,即可实现这两个效果。
第三版
9. Symbol 值可以作为标识符,用于对象的属性名,可以保证不会出现同名的属性。
看着好像没什么,这点其实和第 8 点是冲突的,这是因为当我们模拟的所谓 Symbol 值其实是一个有着 toString 方法的 对象,当对象作为对象的属性名的时候,就会进行隐式类型转换,还是会调用我们添加的 toString 方法,对于 Symbol('foo') 和 Symbol('foo')两个 Symbol 值,虽然描述一样,但是因为是两个对象,所以并不相等,但是当作为对象的属性名的时候,都会隐式转换为
Symbol(foo)
字符串,这个时候就会造成同名的属性。举个例子:为了防止不会出现同名的属性,毕竟这是一个非常重要的特性,迫不得已,我们需要修改 toString 方法,让它返回一个唯一值,所以第 8 点就无法实现了,而且我们还需要再写一个用来生成 唯一值的方法,就命名为 generateName,我们将该唯一值添加到返回对象的 __Name__ 属性中保存下来。
此时再看下这个例子:
第四版
我们再看看接下来的特性。
** 7.Symbol 值不能与其他类型的值进行运算,会报错。**
以
+
操作符为例,当进行隐式类型转换的时候,会先调用对象的 valueOf 方法,如果没有返回基本值,就会再调用 toString 方法,所以我们考虑在 valueOf 方法中进行报错,比如:看着很简单的解决了这个问题,可是如果我们是显式调用 valueOf 方法呢?对于一个原生的 Symbol 值:
是的,对于原生 Symbol,显式调用 valueOf 方法,会直接返回该 Symbol 值,而我们又无法判断是显式还是隐式的调用,所以这个我们就只能实现一半,要不然实现隐式调用报错,要不然实现显式调用返回该值,那……我们选择不报错的那个吧,即后者。
我们迫不得已的修改 valueOf 函数:
第五版
10. Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。
嗯,无法实现。
11. 有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
这个实现类似于函数记忆,我们建立一个对象,用来储存已经创建的 Symbol 值即可。
12. Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。
遍历 forMap,查找该值对应的键值即可。
完整实现
综上所述:
无法实现的特性有:1、4、7、8、10
可以实现的特性有:2、3、5、6、9、11、12
最后的实现如下:
ES6 系列
ES6 系列目录地址:https://github.com/mqyqingfeng/Blog
ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
The text was updated successfully, but these errors were encountered: