• 定义一个常量,它真的不变吗?


    定义一个常量,它真的不变吗?

    JavaScript中一旦被定义就无法再被修改的变量,称之为常量。

    ES6中通过const定义常量,常量通常用大写字母定义,多个单词之间用_分隔。

    const定义常量后,如果修改常量会报错:

    const PI = Math.PI;
    PI = 100;
    
    • 1
    • 2

    这是const定义常量的特点之一。

    但当我们使用const 定义常量,而赋值的是一个引用类型值,再修改常量可不一定报错了!!!

    const SKILLS = {
      CSS: 1 ,
      JS: 2,
      HTML: 3
      WEB_GL: 4
    }
    SKILLS.CSS = 2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    const SKILLS_MAP = new Map([['CSS',1],['JS',2]])
    SKILLS_MAP.set('HTML',3)
    
    
    • 1
    • 2
    • 3

    上述示例代码,即使进行了修改,它也不会出现任何报错。

    回到真实企业项目,团队开发团队中每一位成员技术水平肯定是有区别的。

    当定义一个全局常量const SKILLS = {CSS: 1 ,JS: 2,HTML: 3,WEB_GL: 4} ,如果团队成员分别对SKILLS进行增删改查,由于不会报错极可能不小心造成SKILLS 值被修改,影响到其他依赖SKILLS常量的功能模块。

    const.js

    export const SKILLS = {CSS: 1 ,JS: 2,HTML: 3,WEB_GL: 4}
    
    • 1

    成员a:

    xxx.js

    import {SKILLS} from "const.js"
    SKILLS.delete('CSS')
    SKILLS.set("TS",1)
    //...
    
    • 1
    • 2
    • 3
    • 4

    成员b

    yyy.js

    import {SKILLS} from "const.js"
    const css = SKILLS.CSS
    //...
    
    
    • 1
    • 2
    • 3
    • 4

    如上述,成员a在xxx.js文件中删除SKILLS 中属性CSS,而成员b在yyy.js文件中读取SKILLS 中属性CSS 。如果xxx.js优先执行,yyy.js文件中SKILLS.CSS 肯定为undefined,将会影响yyy.js文件后续的逻辑。

    项目、团队越大,类似问题出现的机率越高,Bug防不胜防

    这时候,常量修改报错就非常重要了!!!。

    本文目的就是记录让其报错的方法。

    对象、数组只读

    Deep freeze object - 30 seconds of code中已经有示例,关键是Object.freeze()方法,它可以冻结一个对象。一个被冻结的对象再也不能被修改,由此达到对象、数组只读的目的。

    对象、数组进行遍历,判断值类型,递归手段进行深层冻结:

    const getType = (value) => {
        const reg = /^\[object\s(\w+)\]$/;
        const type = toString.call(value);
        return reg.exec(type)[1]
    }
    const or = (param1, param2) => param1 || param2;
    const isObject = (value) => {
        const type = getType(value);
        return type === 'Object'
    }
    const isArray = (value) => {
        return Array.isArray(value)
    }
    const isFunc = (callBack) => {
        let type = typeof callBack
        return type === 'function'
    }
    const each = (origin, callBack) => {
        const isArr = isArray(origin)
        if (!or(isObject(origin), isArr)) return;
        if (!isFunc(callBack)) return
        const keys = Object.keys(origin);
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            callBack(isArr ? i : key, origin[key], origin);
        }
    }
    const types = [getType([]), getType({})]
    function deepFreeze(obj) {
        const type = getType(obj)
        if (!types.includes(type)) return obj
        if (or(isObject(obj), isArray(obj))) {
            each(obj, (key, value) => {
                if (typeof value === "object") {
                    obj[key] = deepFreeze(value)
                }
            })
            return Object.freeze(obj)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    测试:

    const obj = deepFreeze({ name: 'deepFreeze', age: 20 })
    const arr = deepFreeze([1,2])
    delete obj.name
    arr[1] = 'deep'
    console.log(obj);//{ name: 'deepFreeze', age: 20 }
    console.log(arr);//[1,2]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    delete obj.name 删除对象属性;arr[1] = 'deep' 重新赋值;它们均不会修改原数据,但也没有报错。

    我要它报错,它居然没有给我报错,为啥呢?🥶

    官网MDN给出了答案:

    严格模式下,才会进行报错。那我们开启严格模式,文件开头添加'use strict'

    'use strict'
    //...
    
    • 1
    • 2

    这个时候再次执行就会出现我们想看到的报错结果了。

    Set、Map只读

    Object.freeze() 对数据、对象可以冻结,但却无法阻止Set、Map类型的增删改。

    const SKILLS_MAP =  Object.freeze(new Map())
    SKILLS_MAP.set(1,2)
    
    
    • 1
    • 2
    • 3

    Map.set()是一个方法。

    如果我们在Map.set 时报错,或重新赋值覆盖原有Map.set()方法逻辑Map.set = function (){throw new Error('..')} 这样即可满足我们的目的。

    又有一个问题是我们怎么知道Map.set 何时何地会被调用呢?

    有人也许会提出使用Map原型:

    Map.prototype.set = ()=>{throw new Error('')}
    
    • 1

    可是修改原型,将导致正常的Map对象不能修改,明显不能满足我们的需求。

    const m =  Map()
    m.set(1,2);//报错
    
    • 1
    • 2

    其实,我们可以参考Vue3响应性原理,对Map、Set 数据源进行Proxy代理捕获。

    //...
    const isSet = (obj) => getType(obj)==='Set'
    const isMap = (obj) => getType(obj)==='Map'
    const keys = ['add', 'delete', 'set']
    //...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    首先定义一个keys,用于存储Set、Map 中会修改源数据的方法名。

    //...
    function deepFreeze(obj) {
        const type = getType(obj)
        if (!types.includes(type)) return obj
        if (or(isObject(obj), isArray(obj))) {
            each(obj, (key, value) => {
                if (typeof value === "object") {
                    obj[key] = deepFreeze(value)
                }
            })
            return Object.freeze(obj)
        }
        //新增代码
        if (or(isMap(obj), isSet(obj))) {
            //遍历Set、Map值,深层处理
            Array.from(obj.values()).forEach(value => {
                deepFreeze(value)
            })
            return new Proxy(obj, {
                get(target, key) {
                    if (keys.includes(key)) {
                        throw new Error('只读,不允许修改!')
                    }
                    return isFunc(target[key]) ? target[key].bind(target) : target[key]
                },
                set() {
                    throw new Error('只读,不允许修改或添加属性!')
                }
            })
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    if (keys.includes(key)) {throw new Error('只读,不允许修改!')} 当key为add, delete, set 之一时,我们需要进行处理,阻止它执行;这里是Map.set 就会抛出错误,当然也可以返回一个函数:

     //...
     if (keys.includes(key)) {
       return ()=>{
         throw new Error('只读,不允许修改!')
       }
    }
    //...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    抛出错误的时机是Map.set() 调用。

     //...
     return isFunc(target[key]) ? target[key].bind(target) : target[key]
     //...
    
    • 1
    • 2
    • 3

    Map.get,Map.size、Map.forEach... 等属性或方法,它们并不会改变源数据,所以我们不做任何处理,该咋样就咋样,注意this 指向即可。

    至此,一个创建对象、数组、Set、Map只读的方法完成了。

    完整代码链接:deepFreeze.ts

    最后

    原创不易!如果我的文章对你有帮助,你的👍就是对我的最大支持_

  • 相关阅读:
    .NET 如何阻止线程执行上下文的传递
    git commit 时 报错 ‘lint-staged‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件
    3.自定义工程目录配置CMakeLists
    搜维尔科技:使用 CyberGlove数据手套进行远程机器人遥操作
    基础shell小技巧004
    Java版工程行业管理系统源码-专业的工程管理软件-提供一站式服务
    私人云盘系统对比
    『手撕Vue-CLI』拷贝模板
    推荐一款调试工具:深蓝串口网络调试工具2022春季版(2.17.4),一直使用这个,最近更新好快。
    爬虫-获取数据xpath
  • 原文地址:https://blog.csdn.net/qq_45472813/article/details/125503147