符号Symbol介绍及应用

前言

随着前端不断的发展,ES5 原本的 6 种数据类型 numberstringbooleanundefinednullobject 已经不够使用,所有 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 控制引用类型转化为基础类型值方法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值