ECMAScript 6:四、Proxy

ECMAScript 6:四、Proxy

1 前言

ES6学习笔记4:Proxy。

学习地址:

https://es6.ruanyifeng.com/#docs/proxy

2 笔记

(1)概述

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

而Java中也常见代理的使用,比如JDK动态代理和CGLIB代理,JDK动态代理是Java原生的代理方式,仅用于接口的代理,而CGLIB代理通过Java字节码实现,效率更高,且更灵活,可直接用于类的代理(通过继承来实现代理)。常见代理的使用方式很多,比如为方法添加日志打印等等。

下面看下Js中的1个栗子:

var obj = new Proxy({}, {
    get: function(target, propKey, receiver){
        console.log(`get ${propKey}`);
        return Reflect.get(target, propKey, receiver);
    },
    set: function(target, propKey, value, receiver){
        console.log(`set ${propKey}`)
        return Reflect.set(target, propKey, value, receiver);
    }
});

上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。

let {log: l} = console;

var obj = new Proxy({}, {
    get: function(target, propKey, receiver){
        console.log(`get ${propKey}`);
        return Reflect.get(target, propKey, receiver);
    },
    set: function(target, propKey, value, receiver){
        console.log(`set ${propKey}`)
        return Reflect.set(target, propKey, value, receiver);
    }
});


obj.count = 1;
l(++obj.count);

// set count
// get count
// set count
// 2 

上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

下面是另一个拦截读取属性行为的例子。

var proxy = new Proxy({}, {
    get: function(target, propKey) {
        if(propKey === 'name')
            return "小徐";
        return 99;
    }
});

l(proxy.time) // 99
l(proxy.name) // 小徐
l(proxy.title) // 99

上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。

注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作

如果handler没有设置任何拦截,那就等同于直接通向原对象

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.names = '小徐';

// 原对象和代理对象均修改成功
l(target.names) // "小徐"
l(proxy.names)  // "小徐"
l(target)       //{ names: '小徐' }
l(proxy)        //{ names: '小徐' }

上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target。

一个技巧是将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用

var object = {proxy: new Proxy(target, handler)};

Proxy实例也可以作为其他对象的原型对象

var proxy = new Proxy({}, {
    get: function(target, propKey) {
        if(propKey === 'name')
            return "小徐";
        return 99;
    }
});

let obj = Object.create(proxy);
l(obj.name) //小徐
l(obj.desc) //99

上面代码中,proxy对象是obj对象的原型,obj对象本身并没有name或者desc属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截

同一个拦截器函数,可以设置拦截多个操作

var handler = {
    get: function(target, name) {
        console.log("get", target)
        console.log("get", name)
        if (name === 'prototype') {
            return Object.prototype;
        }
        return 'Hello, ' + name;
    },

    apply: function(target, thisBinding, args) {
        console.log("apply", target)
        console.log("apply", thisBinding)
        console.log("apply", args)
        return args[0];
    },

    construct: function(target, args) {
        console.log("construct", target)
        console.log("construct", args)
        return {value: args[1]};
    }
};

var fproxy = new Proxy(function(x, y) {
    return x + y;
}, handler);

l(fproxy(1, 2))
// apply [Function (anonymous)]
// apply undefined
// apply [ 1, 2 ]
// 1


l(new fproxy(1, 2))
// construct [Function (anonymous)]
// construct [ 1, 2 ]
// { value: 2 }  


l(fproxy.prototype === Object.prototype)
// get [Function (anonymous)]
// get prototype
// true


l(fproxy.foo === "Hello, foo")
// get [Function (anonymous)]
// get foo
// true

或者handler改为简写入下:

var handler = {
    get(target, name) {
        if (name === 'prototype') {
        return Object.prototype;
        }
        return 'Hello, ' + name;
    },

    apply(target, thisBinding, args) {
        return args[0];
    },

    construct(target, args) {
        return {value: args[1]};
    }
};

对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

get(target, propKey, receiver):
【拦截对象属性的读取,比如proxy.foo和proxy['foo']set(target, propKey, value, receiver):
【拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值】

has(target, propKey):
【拦截propKey in proxy的操作,返回一个布尔值】

deleteProperty(target, propKey):
【拦截delete proxy[propKey]的操作,返回一个布尔值】

ownKeys(target):
【拦截Object.getOwnPropertyNames(proxy)、
Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)for...in循环,返回一个数组。
该方法返回目标对象所有自身的属性的属性名,
而Object.keys()的返回结果仅包括目标对象自身的可遍历属性】

getOwnPropertyDescriptor(target, propKey):
【拦截Object.getOwnPropertyDescriptor(proxy, propKey),
返回属性的描述对象】

defineProperty(target, propKey, propDesc):
【拦截Object.defineProperty(proxy, propKey, propDesc)、
Object.defineProperties(proxy, propDescs),返回一个布尔值】

preventExtensions(target):
【拦截Object.preventExtensions(proxy),返回一个布尔值】

getPrototypeOf(target):
【拦截Object.getPrototypeOf(proxy),返回一个对象】

isExtensible(target):
【拦截Object.isExtensible(proxy),返回一个布尔值】

setPrototypeOf(target, proto):
【拦截Object.setPrototypeOf(proxy, proto),
返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截】

apply(target, object, args):
【拦截 Proxy 实例作为函数调用的操作,
比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)construct(target, args):
【拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

– 总结:Proxy接受两个参数,第一个参数是所要代理的目标对象,第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。要使得Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作。若handler没有设置任何拦截,那就等同于直接通向原对象。Proxy实例可以作为其他对象的原型对象。

(2)Proxy实例的方法

下面是上面这些拦截方法的详细介绍。

(2.1)get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选

get方法的用法,上文已经有一个例子,下面是另一个拦截读取操作的例子。

var person = {
    name: "小徐"
};

var proxy = new Proxy(person, {
    get: function(target, propKey) {
        if (propKey in target) {
            return target[propKey];
        } else {
            throw new ReferenceError("Prop name \"" 
            + propKey + "\" does not exist.");
        }
    }
});

l(proxy.name) 
// "小徐"
l(proxy.age) 
// 抛出错误 ReferenceError: Prop name "age" does not exist.

上面代码逻辑,对person对象进行代理,来代理person对象的属性获取(get方法)。上述逻辑为,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined。

get方法可以继承。

let proto = new Proxy({}, {
    get(target, propertyKey, receiver) {
        console.log('GET ' + propertyKey);
        return target[propertyKey];
    }
});

let obj = Object.create(proto);
l(obj.tool)
// GET tool
// undefined

上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。

下面的例子使用get拦截,实现数组读取负数的索引

function createArray(...elements) {
    let handler = {
        get(target, propKey, receiver) {
            let index = Number(propKey);
            if (index < 0) {
                propKey = String(target.length + index);
            }
            return Reflect.get(target, propKey, receiver);
        }
    };

    let target = [];
    target.push(...elements);
    return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
l(arr[-1]) // 倒数第一个值 c
l(arr[-2]) // 倒数第二个值 b
l(arr[-3]) // 倒数第三个值 a
l(arr[1])  // 正数第二个值 b

上面代码中,数组的位置参数是-1,就会输出数组的倒数第一个成员。

利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作

简单回忆下Array reduce()方法使用方式:

reduce() 方法将数组缩减为单个值。

reduce() 方法为数组的每个值(从左到右)执行提供的函数。

函数的返回值存储在累加器中(结果/总计)。

注释:对没有值的数组元素,不执行 reduce() 方法。

注释:reduce() 方法不会改变原始数组。

Array reduce()栗子1,从头开始减去数组中的数字:

var numbers = [100, 17, 8];

let x = numbers.reduce(doReduce)

console.log(x)
// 75
console.log(numbers)
// [ 100, 17, 8 ]

function doReduce(total, num) {
    return total - num;
}

Array reduce()栗子2,四舍五入数组中的所有数字,并显示总和:

var numbers = [15.5, 1.2, 7.9];

let x = numbers.reduce(doRound, 0)

console.log(x)
// 25 (16 + 1 + 8)
console.log(numbers)
// [ 15.5, 1.2, 7.9 ]

function doRound(total, num) {
    return total + Math.round(num);
}

或者向上取整:

var numbers = [15.5, 1.2, 7.9];

let x = numbers.reduce(doRound, 0)

console.log(x)
// 26 (16 + 2 + 8)
console.log(numbers)
// [ 15.5, 1.2, 7.9 ]

function doRound(total, num) {
    // Math.ceil向上取整,Math.floor向下取整
    return total + Math.ceil(num);
}

然后观察下面的链式操作:

var pipe = function (value) {
    var funcStack = [];

    var oproxy = new Proxy({} , {
        get : function (pipeObject, fnName) {
            console.log("方法名称:", fnName)
            if (fnName === 'get') {
                return funcStack.reduce(function (val, fn) {
                    return fn(val);
                },value);
            }
            // window[fnName]
            // 这里通过node执行,换成类方法PipeFunc.prototype
            funcStack.push(PipeFunc.prototype[fnName]);
            return oproxy;
        }
    });

    return oproxy;
}

class PipeFunc {}
Object.assign(PipeFunc.prototype, {
    double : n => n * 2,
    pow : n => n * n,
    reverseInt : 
    n => n.toString().split("").reverse().join("") | 0
});

// var double = n => n * 2;
// var pow    = n => n * n;
// var reverseInt = n => 
// n.toString().split("").reverse().join("") | 0;

// l(global["double"])     undefined


l(pipe(3).double.pow.reverseInt.get);   //结果:63
// 方法名称: double
// 方法名称: pow       
// 方法名称: reverseInt
// 方法名称: get
 // 63

上述reduce接收两个参数,第一个参数一般是计算后的值,也就是total,第二个参数是数组中的每个值,一般是数字Number等等,但是这里非常巧妙的在数组funcStack中存入的是函数,所以reduce里function的第二个参数实际是链式顺序存入的函数,当链式调用的属性是get时,开始执行reduce逻辑进行链式计算。value是传入的初始值3,执行fn(val)时,第一次就是double(3),所以返回的结果是3*2 = 6;然后是pow(6),结果为 6 * 6 = 36;最后就是reverseInt(36),反序排序后拼接为字符串,即63。

下面看第二个栗子:

function formattedDate(timestamp){
    const date = new Date(timestamp);
    const year = date.getFullYear();  
    const month = (date.getMonth() + 1).toString().padStart(2, '0');  
    const day = date.getDate().toString().padStart(2, '0'); 
    const hour = date.getHours().toString().padStart(2, '0');
    const minute = date.getMinutes().toString().padStart(2, '0');
    const second = date.getSeconds().toString().padStart(2, '0');
    const formattedDate = 
    `${year}-${month}-${day} ${hour}:${minute}:${second}`;  
    return formattedDate
}

function Advice() {
    this.invoke = (interceptor) => {};
}

class Interceptor {
    #interceptorStack = [];
    #currentIndex = -1;
    target;
    args = [];
    constructor(target, args){
        this.target = target;
        this.args = args;
        console.log("this", args, target)
    }

    proceed = () => {
        if(this.#currentIndex == this.#interceptorStack.length - 1) {
            if(this.target === undefined) {
                throw new Error("target access undefined.");
            }
            return this.target(...this.args);
        }

        let intercept = this.#interceptorStack[++this.#currentIndex];
        if(!(intercept instanceof Advice)) {
            throw new Error('Not support advice found.');
        }
        intercept.invoke(this);
    };
    push(advice) {
        if(!(advice instanceof Advice))
            throw new Error("must push advice, other do not allowed.");
        this.#interceptorStack.push(advice);
    }
}

class ReflectiveInterceptor extends Interceptor {
    constructor(target, ...args){
        // super(target, args);

        // 下面这种方式,实际是为子类自身的实例属性赋值,
        // 并不能为父类的args和targets赋值,因为super.XXX实际是为this赋值
        // 当调用父类的时候,比如super.p(),这时super指向父类.prototype
        super();
        // super.target = target;
        // super.args = args;
        // 调用时,父类不存在该值,但是子类存在值,依然获取成功
        // super.target 等于 this.target
        this.target = target;
        this.args = args;
    }
}

class LogAroundAdvice extends Advice {
    constructor(logMessage){
        super();
        this.invoke = (intercept) => {
            console.log(`开始日志打印:${logMessage}.`)
            intercept.proceed();
            console.log(`日志打印结束:${formattedDate(Date.now())}.`)
        }
    }
}

class SecurityAdvice extends Advice {
    constructor(number){
        super();
        this.invoke = (intercept) => {
            console.log(`开始安全检查:${number}.`)
            intercept.proceed();
            console.log(`安全检查结束.`)
        }
    }
}

var interceptors = function (obj) {
    var proxy = new Proxy(obj,
        {
            get(pipeTarget, prop) {
                console.log(pipeTarget)
                console.log(Object.getPrototypeOf(pipeTarget) 
                === TestInter.prototype)
                console.log(Object.getPrototypeOf(pipeTarget)[prop])
                console.log(prop)
                console.log(pipeTarget[prop])

                let reflectiveInterceptor = new 
                ReflectiveInterceptor(
                Object.getPrototypeOf(pipeTarget)[prop], 
                    pipeTarget[prop]);
                reflectiveInterceptor.push(new LogAroundAdvice(prop));
                reflectiveInterceptor.push(new SecurityAdvice(9527));
                return reflectiveInterceptor.proceed();
            }
        }
    );

    return proxy;
}


class TestInter {
    names = "xiaoxu,xiaoli";
    names(...ns) {
        console.log(`你好, ${ns}.`)
    }
}

let i = new interceptors(new TestInter());
i.names

上面通过代理,实现链式的增强效果,执行结果如下(注意上面使用属性名表达式[属性名]获取值,而不是.属性来获取,.属性只会认为这个属性是一个普通的字符串,而非属性名称):

TestInter { names: 'xiaoxu,xiaoli' }
true
[Function: names]
names
xiaoxu,xiaoli
this undefined undefined
开始日志打印:names.
开始安全检查:9527.
你好, xiaoxu,xiaoli.
安全检查结束.
日志打印结束:2024-04-15 23:42:14.

分析下面,可以看到日志打印和安全检查,是环绕式执行的,不影响实际函数的执行结果:你好, xiaoxu,xiaoli.

开始日志打印:names.
开始安全检查:9527.
你好, xiaoxu,xiaoli.
安全检查结束.
日志打印结束:2024-04-15 23:42:14.

上面例子参考java Spring框架中对JDK或CGLIB代理进行链式增强的效果来写的。比如Spring AOP切面,也是类似功能效果,比如不影响原有方法执行的情况下,为方法增加日志打印,方法调用耗时打印,请求参数打印等等。可以理解,此为设计模式中的代理模式,是开发中一种非常实用的设计模式。

下面是一个get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例

const {log:l} = console;

const proxy = new Proxy({}, {
    get: function(target, key, receiver) {
        console.log("receiver", receiver)
        return receiver;
    }
});

l(proxy.getReceiver === proxy) 
// receiver {}
// true

上面代码中,proxy对象的getReceiver属性会被get()拦截,得到的返回值就是proxy对象

const proxy = new Proxy({}, {
    get: function(target, key, receiver) {
        return receiver;
    }
});

const d = Object.create(proxy);
l(d.a === d) // true
l(d.names === d) // true

上面代码中,d对象本身没有a(names)属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,receiver就指向d,代表原始的读操作所在的那个对象。

如果一个属性不可配置(configurable)且不可写(writable),则Proxy不能修改该属性,否则通过 Proxy 对象访问该属性会报错

const target = Object.defineProperties({}, {
    names: {
        value: '小徐',
        writable: false,
        configurable: false
    },
});

const handler = {
    get(target, propKey) {
        return 'abc';
    }
};

const proxy = new Proxy(target, handler);

l(proxy.names)
// TypeError: 'get' on proxy: property 'names' is a read-only 
// and non-configurable data property on the proxy target 
// but the proxy did not return its actual value 
// (expected '小徐' but got 'abc')

– 总结:利用 Proxy的get方法可以拦截某个属性的读取操作,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。同时可以通过Proxy代理,实现对原有方法的链式增强,而不影响原有方法的执行(设计模式中的代理模式)。若一个属性不可配置(configurable)且不可写(writable),则Proxy不能修改该属性,否则通过 Proxy 对象访问该属性会报错。

(2.2)set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求。

let validator = {
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('The age is not an integer');
            }
            if (value > 200) {
                throw new RangeError('The age seems invalid');
            }
        }

        // 对于满足条件的 age 属性以及其他属性,直接保存
        obj[prop] = value;
        return true;
    }
};

let person = new Proxy({}, validator);

person.age = 100;
l(person)   //{ age: 100 }

l(person.age)  // 100
person.age = 'young' // 报错  TypeError: The age is not an integer
person.age = 300 // 报错  RangeError: The age seems invalid

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。

有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get和set方法,就可以做到防止这些内部属性被外部读写

const handler = {
    get (target, key) {
        invariant(key, 'get');
        return target[key];
    },
    set (target, key, value) {
        invariant(key, 'set');
        target[key] = value;
        return true;
    }
};

function invariant (key, action) {
    if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
    }
}

const target = {};
const proxy = new Proxy(target, handler);
proxy.names = "小徐";
l(proxy.names)      //小徐


l(proxy._prop)
// Error: Invalid attempt to get private "_prop" property

proxy._prop = '小徐'
// Error: Invalid attempt to set private "_prop" property

上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。

下面是set方法第四个参数的栗子

const handler = {
    set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
        return true;
    }
};

const proxy = new Proxy({}, handler);
proxy.names = '小徐';
l(proxy.names === proxy) // true

上面代码中,set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身,请看下面的例子。

const handler = {
    set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
        return true;
    }
};

const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.names = 'xiaoxu';
l(myObj.names === myObj) // true

上面代码中,设置myObj.names属性的值时,myObj并没有names属性,因此引擎会到myObj的原型链去找names属性。myObj的原型对象proxy是一个Proxy实例,设置它的names属性会触发set方法。这时,第四个参数receiver就指向原始赋值行为所在的对象myObj。

注意,如果目标对象自身的某个属性不可写,那么set方法将不起作用

const obj = {};
Object.defineProperty(obj, 'names', {
  value: '小徐',
  writable: false
});

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = 'xiaoli';
    return true;
  }
};

const proxy = new Proxy(obj, handler);
proxy.names = '小徐';
// 属性不为xiaoli,因为names的writable是false,set无法生效
l(proxy.names) // "小徐"

上述说明不可写的属性,set方法代理是无效的。

注意,set代理应当返回一个布尔值。严格模式下,set代理如果没有返回true,就会报错

'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // 无论有没有下面这一行,都会报错
    return false;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'

上面代码中,严格模式下,set代理返回false或者undefined,都会报错。

注意:在node执行中,下述实际并没有报错(node版本为17.0.1或者18.2.0),对于set方法返回为false的场景,目前代理Proxy不会抛出异常

'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // 实际set返回false,并未报错
    return false;
  }
};

const proxy = new Proxy({}, handler);
proxy.names = 'xiaoli';
l(proxy)    //<ref *1> { names: [Circular *1] }

– 总结:利用 Proxy的set方法可以拦截某个属性的赋值操作。当Proxy设置了存值函数set,对于任何不符合要求的属性赋值可以抛出错误,这是数据验证的一种实现方法。当我们在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用时,通过set方法可以防止这些内部属性被外部读写。若目标对象自身的某个属性不可写(writable是false),那么set方法将不起作用。set代理应当返回一个布尔值。严格模式下,set代理如果没有返回true,就会报错。

(2.3)apply()

apply方法拦截函数的调用、call和apply操作

apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组

var handler = {
    apply (target, ctx, args) {
        return Reflect.apply(...arguments);
    }
};

下面是一个例子。

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

l(p())
// "I am the proxy"

上面代码中,变量p是 Proxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。

下面是另外一个例子。

var twice = {
    apply (target, ctx, args) {
        return Reflect.apply(...arguments) * 2;
    }
};

function sum (left, right) {
    return left + right;
};

var proxy = new Proxy(sum, twice);

l(proxy(1, 2)) // 6
l(proxy.call(null, 5, 6)) // 22
l(proxy.apply(null, [7, 8])) // 30
// bind绑定参数9,10,最后结果是19 * 2 = 38
l(proxy.bind(null, 9, 10)(1, 2)) //38

上面代码中,每当执行proxy函数(直接调用或call和apply调用),就会被apply方法拦截同时可以发现,bind也会被apply方法拦截,bind生成函数后,再次调用依然拦截成功。

另外,直接调用Reflect.apply方法,也会被拦截

l(Reflect.apply(proxy, null, [9, 10])) // 38

– 总结:利用 Proxy的apply方法可以拦截函数的调用、call、apply、bind操作。

(2.4)has()

has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符

has()方法可以接受两个参数,分别是目标对象、需查询的属性名

下面的例子使用has()方法隐藏某些属性,不被in运算符发现

var handler = {
    has (target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
};
var target = { _prop: 'xiaoxu', prop: 'xiaoli'};
var proxy = new Proxy(target, handler);
l('_prop' in proxy) // false
l('prop' in proxy)  // true

class Apple {
    #names = "xiaoli";
    namesTwo = "xiaoxu";
    _names = "both";
}
var proxy2 = new Proxy(new Apple(), handler);

// 私有属性使用in依然是false
l('#names' in proxy2)    // false
l('namesTwo' in proxy2)  // true
l('_names' in proxy2)    // false

上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has()就会返回false,从而不会被in运算符发现。

如果原对象不可配置或者禁止扩展,这时has()拦截会报错

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false;
  }
});

l('a' in p) // TypeError is thrown
// TypeError: 'has' on proxy: trap returned falsish 
// for property 'a' but the proxy target is not extensible

上面代码中,obj对象禁止扩展,结果使用has拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则has()方法就不得“隐藏”(即返回false)目标对象的该属性

值得注意的是,has()方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性

另外,虽然for…in循环也用到了in运算符,但是has()拦截对for…in循环不生效

let stu1 = {name: '张三', score: 59};
let stu2 = {name: '李四', score: 99};

let handler = {
  has(target, prop) {
    if (prop === 'score' && target[prop] < 60) {
      console.log(`${target.name} 不及格`);
      return false;
    }
    return prop in target;
  }
}

let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);

l('score' in oproxy1)
// 张三 不及格
// false

l('score' in oproxy2)
// true

for (let a in oproxy1) {
  console.log(oproxy1[a]);
}
// 张三
// 59

for (let b in oproxy2) {
  console.log(oproxy2[b]);
}
// 李四
// 99

上面代码中,has()拦截只对in运算符生效,对for…in循环不生效,导致不符合要求的属性没有被for…in循环所排除。

– 总结:利用 Proxy的has方法可以拦截HasProperty操作。has()方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性。has()拦截对for…in循环不生效。

(2.5)construct()

construct()方法用于拦截new命令,下面是拦截对象的写法

const handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

construct()方法可以接受三个参数。

target:目标对象。
args:构造函数的参数数组。
newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p)。

const p = new Proxy(function () {}, {
    construct: function(target, args) {
        console.log('called: ' + args.join(', '));
        return { value: args[0] * 10 };
    }
});

l((new p(1)).value)
// "called: 1"
// 10

construct()方法返回的必须是一个对象,否则会报错

const p = new Proxy(function() {}, {
    construct: function(target, argumentsList) {
        return 1;
    }
});

new p() // 报错
// TypeError: 'construct' on proxy: 
// trap returned non-object ('1')

另外,由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错

const p = new Proxy({}, {
    construct: function(target, argumentsList) {
        return {};
    }
});

new p() // 报错
// TypeError: p is not a constructor

上面例子中,拦截的目标对象不是一个函数,而是一个对象(new Proxy()的第一个参数),导致报错。

注意,construct()方法中的this指向的是handler,而不是实例对象

const handler = {
    construct: function(target, args) {
        console.log(this === handler);
        return new target(...args);
    }
}

let p = new Proxy(function () {}, handler);
new p() // true

– 总结:利用 Proxy的construct方法可以拦截new命令。construct()方法返回的必须是一个对象,否则会报错。construct()拦截的是构造函数,若它的目标对象不是函数,则会报错。construct()方法中的this指向的是handler,而不是实例对象。

(2.6)deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除

var handler = {
    deleteProperty (target, key) {
        invariant(key, 'delete');
        delete target[key];
        return true;
    }
};

function invariant (key, action) {
    if (key[0] === '_') {
        throw new Error
        (`Invalid attempt to ${action} private "${key}" 
        property`);
    }
}

var target = { _prop: '小徐' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错

– 总结:利用 Proxy的deleteProperty方法可以拦截delete操作。目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

(2.7)defineProperty()

defineProperty()方法拦截了Object.defineProperty()操作。

var handler = {
    defineProperty (target, key, descriptor) {
        console.log("定义属性,", key)
        return false;
    }
};
var target = {};
var proxy = new Proxy(target, handler);

// 定义属性, names
proxy.names = '小徐' // 不会生效
l(proxy.names)      //undefined

上面代码中,defineProperty()方法内部没有任何操作,只返回false,导致添加新属性总是无效。注意,这里的false只是用来提示操作失败,本身并不能阻止添加新属性。

注意,如果目标对象不可扩展(non-extensible),则defineProperty()不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty()方法不得改变这两个设置。

– 总结:利用 Proxy的defineProperty方法可以拦截Object.defineProperty()操作。

(2.8)getOwnPropertyDescriptor()

getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor()返回一个属性描述对象或者undefined

var handler = {
    getOwnPropertyDescriptor (target, key) {
        if (key[0] === '_') {
            return;
        }
        return Object.getOwnPropertyDescriptor(target, key);
    }
};

var target = { _names: 'xiaoxu', names: 'xiaoli' };
var proxy = new Proxy(target, handler);

l(Object.getOwnPropertyDescriptor(proxy, 'age'))
// undefined
l(Object.getOwnPropertyDescriptor(proxy, '_names'))
// undefined
l(Object.getOwnPropertyDescriptor(proxy, 'names'))
// {
//   value: 'xiaoli',
//   writable: true, 
//   enumerable: true,
//   configurable: true
// }

上面代码中,handler.getOwnPropertyDescriptor()方法对于第一个字符为下划线的属性名会返回undefined。

– 总结:利用 Proxy的getOwnPropertyDescriptor方法可以拦截Object.getOwnPropertyDescriptor()操作。

(2.9)getPrototypeOf()

getPrototypeOf()方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。

Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof

下面是一个栗子:

var proto = {};

var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});

l(Object.getPrototypeOf(p) === proto) // true

上面代码中,getPrototypeOf()方法拦截Object.getPrototypeOf(),返回proto对象。

注意,getPrototypeOf()方法的返回值必须是对象或者null,否则报错。另外,如果目标对象不可扩展(non-extensible), getPrototypeOf()方法必须返回目标对象的原型对象

再来看个栗子:

class A{};

var p = new Proxy({}, {
  getPrototypeOf(target) {
    return A.prototype;
  }
});

l(A.prototype.__proto__ === Object.getPrototypeOf(p).__proto__) 
// true
l(A.prototype.isPrototypeOf(p)) // true
l(Object.getPrototypeOf(p) === A.prototype) // true
l(Reflect.getPrototypeOf(p) === A.prototype) // true
l(p instanceof A) // true

– 总结:利用 Proxy的getPrototypeOf方法可以拦截获取对象原型操作。getPrototypeOf()方法的返回值必须是对象或者null,否则报错。如果目标对象不可扩展(non-extensible), getPrototypeOf()方法必须返回目标对象的原型对象。

(2.10)isExtensible()

isExtensible()方法拦截Object.isExtensible()操作

var p = new Proxy({}, {
    isExtensible: function(target) {
        console.log("called");
        return true;
    }
});

// Object.isExtensible() 静态方法判断一个对象是否是可扩展的
// (是否可以在它上面添加新的属性)

l(Object.isExtensible(p))
// "called"
// true

Object.preventExtensions(p)

l(Object.isExtensible(p))   //报错
// 若Object.preventExtensions设置对象不可扩展,则
// 必须返回false
// TypeError: 'isExtensible' on proxy: 
// trap result does not reflect extensibility of proxy target (which is 'false')

上面代码设置了isExtensible()方法,在调用Object.isExtensible时会输出called。

注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值

这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。

Object.isExtensible(proxy) === Object.isExtensible(target)

下面是一个栗子:

var p = new Proxy({}, {
    isExtensible: function(target) {
        return false;
    }
});

Object.isExtensible(p)
// TypeError: 'isExtensible' on proxy: 
//   trap result does not reflect extensibility of proxy target (which is 'true')

– 总结:利用 Proxy的isExtensible方法可以拦截Object.isExtensible()操作。该方法只能返回布尔值,否则返回值会被自动转为布尔值。且该方法的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。

(2.11)ownKeys()

ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。

Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
for...in循环

下面是拦截Object.keys()的例子。

let target = {
    a: 1,
    b: 2,
    c: 3
};

let handler = {
    ownKeys(target) {
        return ['a'];
    }
};

let proxy = new Proxy(target, handler);

l(Object.getOwnPropertyNames(proxy))    // [ 'a' ]
l(Object.getOwnPropertySymbols(proxy))  // []
l(Object.keys(proxy))                   // [ 'a' ]
l(Reflect.ownKeys(proxy))               // [ 'a' ]
for(let i in proxy) {
    console.log(i)     // a
}

上面代码拦截了对于target对象的Object.keys()操作,只返回a、b、c三个属性之中的a属性。

下面的例子是拦截第一个字符为下划线的属性名。

let target = {
    _names: 'xiaoxu',
    _prop: 'value me',
    age: '15'
};

let handler = {
    ownKeys (target) {
        return Reflect.ownKeys(target).filter(key => key[0] !== '_');
    }
};

let proxy = new Proxy(target, handler);

for (let key of Object.keys(proxy)) {
    console.log(target[key]);
}
// "15"

注意,使用Object.keys()方法时,有三类属性会被ownKeys()方法自动过滤,不会返回。

目标对象上不存在的属性
属性名为 Symbol 值
不可遍历(enumerable)的属性
let target = {
    a: 1,
    b: 2,
    c: 3,
    [Symbol.for('secret')]: '4',
};

Object.defineProperty(target, 'key', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: 'static'
});

let handler = {
    ownKeys(target) {
        return ['a', 'd', Symbol.for('secret'), 'key'];
    }
};

let proxy = new Proxy(target, handler);

l(Object.keys(proxy))
// ['a']
l(Object.getOwnPropertySymbols(proxy))
// [ Symbol(secret) ]
l(Object.getOwnPropertyNames(proxy))
// [ 'a', 'd', 'key' ]
l(Reflect.ownKeys(proxy)) 
// [ 'a', 'd', Symbol(secret), 'key' ]
for(let i in proxy) {
    console.log(i)     // a
}

上面代码中,ownKeys()方法之中,显式返回不存在的属性(d)、Symbol 值(Symbol.for(‘secret’))、不可遍历的属性(key),结果都被自动过滤掉Object.keys(proxy)返回结果和for in 结果一致,其余方法返回有一定区别

ownKeys()方法还可以拦截Object.getOwnPropertyNames()(上面也有演示)。

var p = new Proxy({}, {
    ownKeys: function(target) {
        return ['a', 'b', 'c'];
    }
});

l(Object.getOwnPropertyNames(p))
// [ 'a', 'b', 'c' ]

for…in循环也受到ownKeys()方法的拦截(上面也有演示)。

const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
  ownKeys: function () {
    return ['a', 'b'];
  }
});

for (let key in proxy) {
  console.log(key); // 没有任何输出
}

上面代码中,ownkeys()指定只返回a和b属性,由于obj没有这两个属性,因此for…in循环不会有任何输出。

ownKeys()方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错

var obj = {};

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return [123, true, undefined, null, {}, []];
  }
});

l(Object.getOwnPropertyNames(p))
// TypeError: 123 is not a valid property name

把上述的多个值都修改为String后,执行才不报错(Number、Boolean、null、undefined、对象、数组等都不允许在ownKeys的返回中出现):

var obj = {};

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return ["123", "true", "undefined", "null", "{}", "[]"];
  }
});

l(Object.getOwnPropertyNames(p))
// [ '123', 'true', 'undefined', 'null', '{}', '[]' ]

如果目标对象自身包含不可配置的属性,则该属性必须被ownKeys()方法返回,否则报错

var obj = {};
Object.defineProperty(obj, 'a', {
  configurable: false,
  enumerable: true,
  value: 10 }
);

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return ['b'];
  }
});

l(Object.getOwnPropertyNames(p))
// TypeError: 'ownKeys' on proxy: trap result did not include 'a'

上面代码中,obj对象的a属性是不可配置的,这时ownKeys()方法返回的数组之中,必须包含a,否则会报错。

另外,如果目标对象是不可扩展的(non-extensible),这时ownKeys()方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错

var obj = {
    a: 1
};

Object.preventExtensions(obj);

var p = new Proxy(obj, {
    ownKeys: function(target) {
        return ['a', 'b'];
    }
});

l(Object.getOwnPropertyNames(p))
// TypeError: 'ownKeys' on proxy: 
// trap returned extra keys but proxy target is non-extensible

上面代码中,obj对象是不可扩展的,这时ownKeys()方法返回的数组之中,包含了obj对象的多余属性b,所以导致了报错。

– 总结:利用 Proxy的ownKeys方法可以拦截对象自身属性的读取操作。ownKeys()方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。若目标对象自身包含不可配置的属性,则该属性必须被ownKeys()方法返回,否则报错。如果目标对象是不可扩展的(non-extensible),这时ownKeys()方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。

(2.12)preventExtensions()

preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。

这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。

var proxy = new Proxy({}, {
    preventExtensions: function(target) {
        return true;
    }
});

l(Object.preventExtensions(proxy))
// TypeError: 'preventExtensions' on proxy: 
// trap returned truish but the proxy target is extensible

上面代码中,proxy.preventExtensions()方法返回true,但这时Object.isExtensible(proxy)会返回true,因此报错。

为了防止出现这个问题,通常要在proxy.preventExtensions()方法里面,调用一次Object.preventExtensions()。

var proxy = new Proxy({}, {
    preventExtensions: function(target) {
        console.log('called');
        Object.preventExtensions(target);
        return true;
    }
});

l(Object.preventExtensions(proxy))
// called
// {}

– 总结:利用 Proxy的preventExtensions()方法拦截Object.preventExtensions()。只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。

(2.13)setPrototypeOf()

setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。

下面是一个例子。

var handler = {
    setPrototypeOf (target, proto) {
        throw new Error('Changing the prototype is forbidden');
    }
};

var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
l(Object.setPrototypeOf(proxy, proto))
// Error: Changing the prototype is forbidden

上面代码中,只要修改target的原型对象,就会报错。

注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf()方法不得改变目标对象的原型

– 总结:利用 Proxy的setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。

(3)Proxy.revocable()

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.names = "xiaoxu";
l(proxy.names) // xiaoxu

revoke();
l(proxy.names) 
// TypeError: Cannot perform 'get' on a proxy that has been revoked 

Proxy.revocable()方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问

(4)this问题

虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理

const target = {
    m: function () {
        console.log(this === proxy);
    }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

上面代码中,一旦proxy代理target,target.m()内部的this就是指向proxy,而不是target。所以,虽然proxy没有做任何拦截,target.m()和proxy.m()返回不一样的结果。

下面是一个例子,由于this指向的变化,导致 Proxy 无法代理目标对象。

const _name = new WeakMap();

class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

const person = new Person('小徐');
l(person.name) // '小徐'

const proxy = new Proxy(person, {});
l(proxy.name) // undefined

上面代码中,目标对象person的name属性,实际保存在外部WeakMap对象_name上面,通过this键区分。由于通过proxy.name访问时,this指向proxy,导致无法取到值,所以返回undefined。

此外,有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

上面代码中,getDate()方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错。这时,this绑定原始对象,就可以解决这个问题

const target = new Date('2024-04-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);

l(proxy.getDate()) // 1

另外,Proxy 拦截函数内部的this,指向的是handler对象

const handler = {
    get: function (target, key, receiver) {
        console.log("get", this === handler);
        return 'Hello, ' + key;
    },
    set: function (target, key, value) {
        console.log("set", this === handler);
        target[key] = value;
        return true;
    }
};

const proxy = new Proxy({}, handler);

l(proxy.names)
// get true
// Hello, names

proxy.names = 1
// set true

上面例子中,get()和set()拦截函数内部的this,指向的都是handler对象。

(5)实例:Web服务的客户端

Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。

const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});

上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}

同理,Proxy 也可以用来实现数据库的ORM层。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/552086.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【智能排班系统】Quartz结合Cron-Utils自定义时间发送上班、休息提醒

文章目录 Quartz&#xff1a;强大的Java作业调度引擎Quartz概述核心概念与架构配置文件主配置&#xff08;配置主要调度器设置、事务&#xff09;线程池配置&#xff08;调整作业执行资源&#xff09;SimpleThreadPool特定属性自定义线程池 RAMJobStore配置&#xff08;在内存中…

人工智能揭示矩阵乘法的新可能性

人工智能揭示矩阵乘法的新可能性 数学家酷爱漂亮的谜题。当你尝试找到最有效的方法时&#xff0c;即使像乘法矩阵&#xff08;二维数字表&#xff09;这样抽象的东西也会感觉像玩一场游戏。这有点像尝试用尽可能少的步骤解开魔方——具有挑战性&#xff0c;但也很诱人。除了魔方…

基于GIS、python机器学习技术的地质灾害风险评价与信息化建库应用

结合项目实践案例和科研论文成果进行讲解。入门篇&#xff0c;ArcGIS软件的快速入门与GIS数据源的获取与理解&#xff1b;方法篇&#xff0c;致灾因子提取方法、灾害危险性因子分析指标体系的建立方法和灾害危险性评价模型构建方法&#xff1b;拓展篇&#xff0c;GIS在灾害重建…

IEDA 的各种常用插件汇总

目录 IEDA 的各种常用插件汇总1、 Alibaba Java Coding Guidelines2、Translation3、Rainbow Brackets4、MyBatisX5、MyBatis Log Free6、Lombok7、Gitee IEDA 的各种常用插件汇总 1、 Alibaba Java Coding Guidelines 作用&#xff1a;阿里巴巴代码规范检查插件&#xff0c;…

JavaScript之分时函数、分时间段渲染页面、提高用户体验、参数归一化、高阶函数、分段、appendChild、requestIdleCallback

MENU 前言效果图html原始写法优化方式一(参数归一化)优化方式二(当浏览器不支持requestIdleCallback方法的时候)优化方式三(判断环境) 前言 当前需要向页面插入十万个div元素&#xff0c;如果使用普通的渲染方式&#xff0c;会造成延迟。这时候就需要通过分时函数来实现渲染了。…

[element] 简单封装一个表格展示

简单封装 如果你想呈现一个表格,直接复制案例的话是这样的,圈出来的你需要写进入,麻烦 这时候把需要显示的列数据弄成一个对象数组, 给它列名和标题就行 记得这个prop和源数据的prop要对应!! const columns [{label: "日期",prop: date},{label: "姓名",…

【管理咨询宝藏72】MBB大型城投集团能源板块行业分析报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏72】MBB大型城投集团能源板块行业分析报告 【格式】PDF版本 【关键词】战略规划、商业分析、管理咨询、MBB顶级咨询公司 【强烈推荐】 这是一套…

通讯录的实现(顺序表)

前言&#xff1a;上篇文章我们讲解的顺序表以及顺序表的具体实现过程&#xff0c;那么我们的顺序表在实际应用中又有什么作用呢&#xff1f;今天我们就基于顺序表来实现一下通讯录。 目录 一.准备工作 二.通讯录的实现 1.通讯录的初始化 2.插入联系人 3.删除联系人 4.…

Arthas实战教程:定位Java应用CPU过高与线程死锁

引言 在Java应用开发中&#xff0c;我们可能会遇到CPU占用过高和线程死锁的问题。本文将介绍如何使用Arthas工具快速定位这些问题。 准备工作 首先&#xff0c;我们创建一个简单的Java应用&#xff0c;模拟CPU过高和线程死锁的情况。在这个示例中&#xff0c;我们将编写一个…

OpenHarmony C/C++三方库移植适配

简介 众所周知&#xff0c;C/C三方库相对与JS/ETS的三方组件来说&#xff0c;其运行效率高。那如何将一个C/C三方库移植到OH系统上呢&#xff1f;本文将介绍如何快速高效的移植一个C/C三方库到OpenHarmony上。 C/C三方库适配问题与解决方案 由上图可以看出&#xff0c;三方库…

Ypay源支付前端美化模板

功能&#xff1a; 首页加了运行时间&#xff0c;加了首页一言打字效果&#xff0c;加了访问次数&#xff0c;还有底部也适当的加了一点美化 而且加了一个播放器功能&#xff0c;可以自定义歌曲之类的 完美契合于源支付 直接上传主题包使用即可 演示图: 使用: 请不要在后台…

C语言学习笔记之指针(一)

目录 什么是指针&#xff1f; 指针和指针类型 指针的类型 指针类型的意义 指针-整数 指针的解引用 指针 - 指针 指针的关系运算 野指针 什么是野指针&#xff1f; 野指针的成因 如何规避野指针&#xff1f; 二级指针 什么是指针&#xff1f; 在介绍指针之前&#…

Ubuntu上安装Chrome浏览器

安装步骤 1.下载安装chrome安装包 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb2.安装Chrome浏览器 sudo dpkg -i google-chrome-stable_current_amd64.debsudo apt-get -f install3.启动Chrome浏览器 查看收藏夹里的Chrome图标 单击C…

LeetCode刷题总结 | 图论3—并查集

并查集理论基础 1.背景 首先要知道并查集可以解决什么问题呢&#xff1f; 并查集常用来解决连通性问题。大白话就是当我们需要判断两个元素是否在同一个集合里的时候&#xff0c;我们就要想到用并查集。 并查集主要有两个功能&#xff1a; 将两个元素添加到一个集合中。判…

python怎么连接oracle

一&#xff1a;弄清版本&#xff0c;最重要&#xff01;&#xff01;&#xff01; 首先安装配置时&#xff0c;必须把握一个点&#xff0c;就是版本一致&#xff01;包括&#xff1a;系统版本&#xff0c;python版本&#xff0c;oracle客户端的版本&#xff0c;cx_Oracle的版本…

IAR 使用笔记(IAR BIN大小为0异常解决)

烧写 由于芯片的内部SPI FLASH的0级BOOT 程序起到到开启JTAG SW 仿真功能&#xff0c;一旦内部SPI FLASH存储的BL0启动代码被损坏&#xff0c;芯片的JTAG 将不能被连接。所以对BL0的烧写需要谨慎&#xff0c;烧写BL0过程保证芯片不断电。 如果烧写了多备份的启动代码&#xff…

深度学习架构(CNN、RNN、GAN、Transformers、编码器-解码器架构)的友好介绍。

一、说明 本博客旨在对涉及卷积神经网络 &#xff08;CNN&#xff09;、递归神经网络 &#xff08;RNN&#xff09;、生成对抗网络 &#xff08;GAN&#xff09;、转换器和编码器-解码器架构的深度学习架构进行友好介绍。让我们开始吧&#xff01;&#xff01; 二、卷积神经网络…

【Java探索之旅】掌握数组操作,轻松应对编程挑战

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、数组巩固练习1.1 数组转字符串1.2 数组拷贝1.3 求数组中的平均值1.4 查找数组中指…

手写签名功能(vue3)

手写签名功能&#xff08;vue3&#xff09; 效果 显示效果 签名版效果 代码 代码引入 写成子组件形式&#xff0c;直接引入即可 <signature-features />代码结构 signatureFeatures&#xff1a;签名的显示效果 vueEsign&#xff1a;画板 xnSignName&#xff1a;打开…

Ubuntu修改DNS

【永久修改DNS】 临时修改DNS的方法是在 /etc/resolv.conf 添加&#xff1a;nameserver 8.8.8.8 nameserver 8.8.8.8 注意到/etc/resolv.conf最上面有这么一行&#xff1a; DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN 说明重启之后这个文件会被自动…
最新文章