前言
随着前端不断的发展,ES5
原本的 6 种数据类型 number
、string
、boolean
、undefined
、null
、object
已经不够使用,所有 ES6
新增符号数据类型 symbol
基础类型,相信大家会发现在工作中很少会使用到 symbol
从而导致认为符号没有什么作用,而实际上符号的出现减少了 js
中一些魔法使用,下面我们就对符号 symbol
深入学习
符号是ES6新增的一个数据类型,它通过使用函数
Symbol(符号描述)
来创建符
符号设计的初衷,是为了给对象设置私有属性
共享符号可以根据某个符号名称(符号描述)能够得到同一个符号,通过使用函数Symbol.for("符号名/符号描述")
来创建
Symbol 特点
- 没有字面量(每个
Symbol
都是通过Symbol
函数创建) - 每次调用 Symbol 函数得到的符号永远不相等,无论符号名是否相同
- 符号可以作为对象的属性名存在,这种属性称之为符号属性(
es6
之前对象属性只能是字符串或者数字) - 符号无法被隐式转换,不能被用于数学运算、字符串拼接或其他隐式转换,但符号可以通过 String 构造函数显式的转换为字符串
- 不能被
new
执行(不能进行构造函数) 但是有原型
Symbol.iterator
该符号可以判断数据是否拥有迭代功能,它可以影响数据迭代器遍历功能
// 对象默认不具备 Symbol.iterator 属于不可以被遍历的数据结构
// 具有可迭代数据有: argument / NodeList / HTMLcollection / String / Set / Map / generator
// 改写对象使对象也具有 for of 遍历
Object.prototype[Symbol.iterator] = function () {
let self = this;
let keys = Reflect.ownKeys(this); // 获取所有 key 属性只包含 Symbol 符号属性
let index = 0;
return {
next() {
let key = keys[index++];
// 遍历结束
if (index > keys.length) {
return {
done: true,
value: undefined
}
}
return {
done: false,
value: {
key,
value: self[key]
}
}
}
}
}
// 测试原本对象不具有 for of 现在已经具备
var ob = {
a: 1,
b: 2,
c: 3,
d: 5
}
for (var key of ob) {
console.log(key);
}
// 由上面结果可知我们可以使用 Symbol.iterator 进行一些不具备迭代数据进行改造以此满足需求
Symbol.hasInstance
该符号用于定义构造函数的静态成员,它将影响 instanceof 的判定
// instanceof 实现原理是 obj instanceof fn ==> fn[Symbol.hasInstance](obj) 先查看原型中 fn[Symbol.hasInstance](obj) 返回值
// 写法一(构造函数)
function A() {
}
Object.defineProperty(A, Symbol.hasInstance, {
value: function (obj) {
return false; // 所有都返回 false
}
})
const obj = new A();
console.log(A[Symbol.hasInstance](obj)); // false
console.log(obj instanceof A); // 实际等同上面面写法
// 写法二(类)
class Fn {
constructor() {
this.x = Symbol.for("xxx"); // 设置知名符号
}
static [Symbol.hasInstance](obj) {
return obj.x && obj.x === Symbol.for("xxx");
}
}
// 方法二只要不是 Fn 创建的构造函数使用 instanceof 返回都是 false
Symbol.isConcatSpreadable
该知名符号会影响数组的 concat 方法
// 数组中使用 concat 方法合并根据连接的数据是否为数组或者类数组进行分割合并
const arr = [3];
const arr2 = [5, 6, 7, 8];
arr2[Symbol.isConcatSpreadable] = false;
const result = arr.concat(56, arr2)
console.log(result) // [3, 56, [5,6,7,8]]
// 类数组
var obj = {
0: 8,
1: 9,
2: 10,
length: 3,
[Symbol.isConcatSpreadable]: true, // 使用时会进行类数组拆分为数组每项进行拼接,如果不设置则直接连接类数组对象
}
const result2 = arr.concat(4, obj);
console.log(result2); // [ 3, 4, 8, 9, 10 ]
// 由上面可值类数组设置 Symbol.isConcatSpreadable 时可以进行拆分每项进行添加而不是添加整体
Symbol.toPrimitive
该知名符号会影响类型转换的结果
// Symbol.toPrimitive 知名符号可以改变引用类型转化为基础类型结果
class Temperature {
constructor(degree) {
this.degree = degree;
}
// 注意不是静态方法哦
[Symbol.toPrimitive](type) {
// type: "default" "string" "number"
if (type === "default") {
return this.degree + "摄氏度";
}
else if (type === "number") {
return this.degree;
}
else if (type === "string") {
return this.degree + "℃";
}
}
}
const t = new Temperature(30);
console.log(t + ""); // 30摄氏度
console.log(t / 2); // 15
console.log(String(t)); // 30C
// 请问下面函数参数为何时候能打印
function isTrue(o) {
// 注意不要全等不然真没有结果
if (o == 1 && o == 2 && o == 3) {
console.log("ojbk");
}
}
// 方法一
var o = {
a: 1,
[Symbol.toPrimitive](hint) {
return this.a ++;
}
}
// 方法二
var o = {
a: 1,
valueOf() {
return this.a++;
}
}
// 方法三
var o = {
a: 1,
toString() {
return this.a++;
}
}
// 此题就是利用引用类型隐式转化为基本类型首先看 [Symbol.toPrimitive]() 然后看 valueOf() 最后看 toString()
Symbol.toStringTag
该知名符号会影响 Object.prototype.toString 的返回值
// Symbol.toStringTag 可以实现使用 toString 判断类型时更加精确的知道自身的构造函数
// 写法一
class Person {
constructor() {
// 通过 Symbol.toStringTag 改写 Object.prototype.toString.call 更准确
this[Symbol.toStringTag] = "Person"
}
}
// 写法二
Person .prototype[Symbol.toStringTag] = "FN" // 等价上面写法
const p = new Person();
const arr = [];
console.log(Object.prototype.toString.apply(p)); //[object Person]
console.log(Object.prototype.toString.apply(arr)) //[object Array]
// 由上面可知我们可以改造 toString() 返回更加精确的值
Symbol 应用
- 对象设置私有属性,(类中设置外部不能访问的属性方法)
- 给对象设置唯一属性值,(实现
Function.prototype.call
方法) - 框架中
redux/vuex
公共管理状态管理时候,派发的行为标识可以基于Symbol
类型进行宏管理
实现类中私有属性
// 实现类中私有属性使其外面不能访问到只能内部使用
const Hero = (() => {
const getRandom = Symbol();
return class {
constructor(a) {
this.a= a;
}
attack() {
const dmg = this.a* this[getRandom](0.8, 1.1);
console.log(dmg);
}
// 私有属性随机函数
[getRandom](min, max) {
return Math.random() * (max - min) + min;
}
}
})();
const h = new Hero(3);
h.attack(); // 不报错
h[Symbol]() // 报错
// 但是我们也可以使用一些不寻常的手段获取使用
const sybs = Object.getOwnPropertySymbols(Hero.prototype);
console.log(h[sybs[0]](3)); // 不报错
// 但是强烈不建议上面使用类中的私有属性
手写实现
call
方法
// 手写实现自己 mycall 方法
Function.prototype.myCall = function (content, ...arg) {
content = (content === undefined || content === null) ? (typeof window === "undefined" ? {} : window) : Object(content);
// 这是确定 this 指向判断是否node环境,如果传入基础类型进行变为包装类型
let key = Symbol(); // 唯一值
content[key] = this; // 将 方法绑定到对象唯一属性上
let result = content[key](...arg); // 函数可能有返回值
delete content[key]; // 用完要删除该唯一属性
return result
}
// 同理我们可以基于 Symbol 符号封装 apply 方法
实现更准确
instanceof
// 重写 instanceof 方法确保不备被别人重写
class fn {
constructor() {
this.x = Symbol.for("xxx");
}
// 设置这个是让 instanceof 不能通过原型链改变, 因为原有的 instanceof 是判断实列是否为构造函数容易改写原型
static [Symbol.hasInstance](obj) {
return obj.x && obj.x === Symbol.for("xxx");
}
}
let f = new fn();
console.log(f instanceof fn) // true
// instanceof 实现原理是 obj instanceof fn ==> fn[Symbol.hasInstance](obj) 先查看原型中 fn[Symbol.hasInstance](obj) 返回值
let arr = [1, 2, 3, 4];
Object.setPrototypeOf(arr, fn.prototype); // 设置原型
console.log(arr instanceof fn); // false
// 由上面可知虽然上面设置原型但是现在判断依然为 false
实现更准确
toString
返回值
// 实现下面代理
// console.log(Object.prototype.toString.call(f)); ==> "[object,FN]"
// 写法一
class FN {
constructor() {
// 通过 Symbol.toStringTag 改写 Object.prototype.toString.call 更准确
this[Symbol.toStringTag] = "FN"
}
}
// 写法二
FN.prototype[Symbol.toStringTag] = "FN" // 等价方法一
let AA = new FN();
console.log(Object.prototype.toString.call(AA)) // "[object,FN]"
// console.log(AA.constructor == FN) // 实例的 constructor 指向构造函数
// console.log(AA.__proto__ == FN.prototype) // 实例的 __proto__ 指向构造函数的原型
实现对象具有遍历功能
// 实现所有对象都具备 for of 遍历功能
Object.prototype[Symbol.iterator] = function () {
let self = this;
let keys = Reflect.ownKeys(self); // 获取所有的对象属性包括符号属性
let index = 0;
return {
next() {
if (index > keys.length - 1) {
return {
done: true,
value: undefined
}
}
let key = keys[index++]; // 保存到外部因为避免两次 ++
return {
done: false,
value: { key, value: self[key] }
}
}
}
}
// 以上使用到 Reflet.ownKeys() 为反射功能,能获取所有对象属性
总结
通过上面的知名符号的学习是不是相当于发现了新大陆,原来符号 symbol
还能这么玩,因为有了符号的加入,所以我们就能更好的控制魔法的语法,也从而让我们知道一些语法底层是怎么实现的。
Symbol.iterator
可以实现改造迭代器使其具备for of
功能Symbol.hasInstance
可以改写更加精确的instanceof
返回值使其设置原型无效Symbol.toStringTag
可以设置重写的toString
方法值返回更加精确指向自身构造函数Symbol.toPrimitive
控制引用类型转化为基础类型值方法