• JS元编程


     如果说常规编程是写代码去操作数据,那么元编程就是写代码去操作其他代码。

    1 属性的特性

    JS的属性有名字和值,但每个属性也有3个关联的特性:

    可写(writable)特性指定是否可修改属性的值。

    可枚举(enumerable)特性指定是否可以通过for/in循环和Object.keys()方法枚举属性。

    可配置(configurable)特性指定是否可以删除属性,以及是否可以修改属性的特性。

    这些特性允许库作者给原型对象添加方法,并让它们像内置方法一样不可枚举;允许作者“锁住”自己的对象,定义不能修改或删除的属性。

    1.1 访问器属性和数值属性

    访问器属性(get或set方法)没有value及writable特性,有4个特性:get、set、enumerable和configurable。

    数值属性的4个特性是:value、writable、enumerable和configurable。

    Object有以下方法来操作这些特性:

    1)  Object.getOwnPropertyDescriptor() 获取特定对象某个属性的属性描述符。

    2) Object.defineProperty() 创建属性并可定义属性描述符。Object.defineProperties() 则可同时定义多个属性及其描述符。

    let obj = {
        get name() { return 'obj' },
        x: 1
    }

    Object.defineProperty(obj,"y",{
        value: 1,
        writable: false,
        enumerable: true,
        configurable: false
    })

    Object.defineProperties(obj,{
        z: {value: 1, writable: true},
        age: { get: () => 18  }
    })

    console.log(obj);
    console.log(Object.getOwnPropertyDescriptor(obj,"name"));
    console.log(Object.getOwnPropertyDescriptor(obj,"z"));
    console.log(Object.getOwnPropertyDescriptor(obj,"age"));

    // { name: [Getter], x: 1, y: 1 }
    // {
    //     get: [Function: get name],
    //     set: undefined,
    //         enumerable: true,
    //     configurable: true
    // }
    // { value: 1, writable: true, enumerable: false, configurable: false }
    // {
    //     get: [Function: get],
    //     set: undefined,
    //         enumerable: false,
    //     configurable: false
    // }

    1.2 自定义深度复制函数

    Object.assign()只复制可枚举属性和属性值,但不复制属性特性。这意味着如果源对象有个访问属性,那么复制到目标对象的是获取函数的返回值。

    let obj = {}

    Object.defineProperties(obj,{
        x: { value: 1, enumerable: true ,writable: false},
        y: { value: 2, enumerable: false, writable: false},
        name: { get: () => "obj",enumerable: true }
    })

    let target = {}
    Object.assign(target, obj);

    console.log(target);
    console.log(Object.getOwnPropertyDescriptor(target,"x"));
    console.log(Object.getOwnPropertyDescriptor(target, "name"))

    // { x: 1, name: 'obj' }
    // { value: 1, writable: true, enumerable: true, configurable: true }
    // { value: 'obj', writable: true, enumerable: true, configurable: true }

    我们自定义个深度复制函数。从一个对象向另一个对象复制属性及它们的特性。

    Object.defineProperty(Object, "assignDeep", {
        writable: false,
        enumerable: false,
        configurable: false,
        value: function (target, ...sourceList) {
            for (let source of sourceList) {
                for (let name of Object.getOwnPropertyNames(source)) {
                    let desc = Object.getOwnPropertyDescriptor(source,name);
                    Object.defineProperty(target,name,desc);
                }
                for (let symbol of Object.getOwnPropertySymbols(source)) {
                    let desc = Object.getOwnPropertyDescriptor(source, name);
                    Object.defineProperty(target,name,desc);
                }
            }
        }
    })

    let target = {};
    let source = { x: 1};
    Object.defineProperties(source, {
        y: { value: 2, enumerable: false},
        name: {get: () => "source"},
        z: { value: 3, enumerable: true}
    });
    Object.assignDeep(target,source);

    console.log(target);
    console.log(Object.getOwnPropertyDescriptor(target,"y"));
    console.log(Object.getOwnPropertyDescriptor(target,"name"));

    // { x: 1, z: 3 }
    // { value: 2, writable: false, enumerable: false, configurable: false }
    // {
    //     get: [Function: get],
    //     set: undefined,
    //         enumerable: false,
    //     configurable: false
    // }

    1.3 对象的可扩展能力

    对象的可扩展特性控制是否可以给对象添加新属性,即是否可扩展。

    Object.isExtensible() 判断一个对象是否可扩展,

    Object.preventExtensions() 让对象不可扩展,只会影响对象本身,而不会影响其原型。

    把对象改为不可扩展是不可逆的。

    2 公认符号

    是Symbol()工厂函数的一组属性,也就是一组符号值。通过这些符号值,我们可以控制js对象和类的某些底层行为。

    1)hasInstance,如果instanceof 的右侧是一个有[Symbol.hasInstance]方法的对象,那么就会以左侧的值作为参数来调用这个方法并返回这个方法的值。

    let bigNum = {
        [Symbol.hasInstance](v) {
            return v > 100
        }
    };

    console.log(12 instanceof  bigNum); // false
    console.log(124 instanceof bigNum); // true

    2)toStringTag, 在调用对象的toString方法时,会查找自己的参数中是否有一个属性的符号名是Symbol.toStringTag,如果有,则使用这个属性的值作为蔬菜。

    class People {
        get [Symbol.toStringTag]() {
            return "People 人";
        }
    }

    class Student {

    }

    console.log(Object.prototype.toString.call(new Student())); // [object Object]
    console.log(new Student()); // {}
    console.log(Object.prototype.toString.call(new People())); // [object People 人]
    console.log(new People()); // People [People 人] {}

    3) 模式匹配符号,match()、matchAll()、search()、replace()和split()这些字符串方法中的任意一个,都有一个与之相对应的公认符号。

    class RegExtCustom {

        constructor(glob) {
            this.glob = glob;
        }

        [Symbol.search](s) {
            return s.search(this.glob);
        }

    }

    let reg = new RegExtCustom("[12]2");
    console.log("1234".search(reg)); // 0
    console.log("ad32".search(reg)); // -1

    3 模版标签

    位于反引号直接的字符串被称为“模版字面量”。如果一个函数的表达式后面跟着一个模版字面量,那就会转换为一个函数调用。

    这个函数第一个参数是一个字符串数组,然后是0或多个额外任何类型的参数。如果模版字面量包含一个要插入的值,那么字符串数组就会收到2个参数,表示这个被插入值两边的字面量。(如果要插入2个值,则字符串数组会收到3个参数)。

    function templateFun(strArr,...args) {
        console.log("字符串数组参数:",strArr);
        console.log("插入参数", args);
    }

    let num = 999;
    let str = 'hello word';
    templateFun`&${num}${str}&`;

    // 字符串数组参数: [ '&', '', '&' ]
    // 插入参数 [ 999, 'hello word' ]

    4 反射与代理对象

    ES6及之后版本中的Proxy类是JS中最强大的元编程特性。使用它可以修改JS对象的基础行为。

    4.1 反射API

    Reflect不是类而是对象,它的属性只是定义了一组相关函数。

    1)apply(f,o,arg),将函数f作为o的方法进行调用(如果o是null,则调用函数f时没有this值),并传入args数组的值作为参数。

    function fun(...args) {
        console.log(this.toString(),args);
    }

    let obj = {
        get [Symbol.toStringTag]() {
            return "自定义obj";
        }
    }

    Reflect.apply(fun,obj,["hello","js"]); // [object 自定义obj] [ 'hello', 'js' ]

    2) getPrototypeof(o), 返回对象o的原型,如果没原型则返回null。

    3) set(o,name,value,receiver), 根据指定的name将对象o的属性值指定为value,如果指定了receiver参数,则将设置方法作为receiver和非o对象的设置方法调用。

    class Student {
        set name(val) {
            console.log("设置名字:",val)
        }
    }

    let student = new Student();
    student.name = "hello student"; // 设置名字: hello student
    Reflect.set(student,"name", "你好啊",(val) => {
        console.log("反射设置值",val);
    }); // 设置名字: 你好啊

    4.2 代理对象

    let proxy = new Proxy(target,handlers); // target 目标对象 handlers处理器对象。 proxy代理对象。

    代理对象没有自己的状态或行为,每次对它执行某个操作,它只会把相应的操作发送给处理器对象或目标对象。(如果处理器对象上存在对应方法,代理就调用该方法,否则执行目标对象对应的方法)。

    function creteCustomProxy(target) {
        let handlers = {
            get(target,property, receiver) {
                console.log(`Handler GET target:${target} property: ${property}`);
                return Reflect.get(target,property,receiver);
            },
            set(target,prop, value, receiver) {
                console.log(`Handler SET target ${target} prop ${prop} value: ${value}`);
                if (prop === 'java') throw new Error("不是JAVA");
                Reflect.set(target,prop,value,receiver);
            }
        };
        return new Proxy(target,handlers);
    }

    let obj = {};
    let proxy = creteCustomProxy(obj);
    proxy.x = "hello";
    console.log(proxy.x);
    proxy.java = "haha";

    // Handler SET target [object Object] prop x value: hello
    // Handler GET target:[object Object] property: x
    // hello
    // Handler SET target [object Object] prop java value: haha
    // /Users/huangzaizai/Desktop/个人/代码/js-study/day4/article/s10.js:9
    // if (prop === 'java') throw new Error("不是JAVA");
    // ^
    //
    // Error: 不是JAVA

    4.2.1 防御性编程

    Proxy.revocable() 函数返回一个对象,包含代理对象和一个revoke()函数,一旦调用revoke(),代理立即失效。

    let obj = {}
    let handle = {}

    let { proxy, revoke } = Proxy.revocable(obj,handle);
    proxy.x = 2;
    console.log(obj); // { x: 2 }
    revoke();
    proxy.y = 2; // 报错 TypeError: Cannot perform 'set' on a proxy that has been revoked

    这个函数的用处是:如果必须向一个不受自己控制的库传一个函数,则可以给它传一个可撤销代理,在使用完这个库之后撤销代理,这样可以防止第三方库持有对你函数的引用。

  • 相关阅读:
    Window MongoDB安装
    OpenCV图像处理基础操作
    I2C3挂载wm8960音频芯片竟如此简单
    linux C语言 手写线程池
    央企基本信息数据集(2008-2022年)
    centos 7部署Mysql8.0主从
    Ansys(Maxwell、Simplorer)与Simulink联合仿真入门
    每日一练:质因数分解
    vue2升级vue3: TSX Vue 3 Composition API Refs
    Redis 获取、设置配置文件
  • 原文地址:https://blog.csdn.net/qq_25308331/article/details/133917518