Proxy代理

Proxy代理

博主wx: -GuanEr-,加博主进前端交流群

什么是 Proxy

proxy 的意思是代理,是为了让对数据的操作变得更安全,我们可以使用proxy拦截某些操作,直接对其进行过滤和改写。官方将这种拦截、过滤和改写称为代理

一个简单的示例

let boy = {
    name: '小明',
    age: 18,
    money: 100
};
let px = new Proxy(boy,{
    get(target, propKey) {
        console.log(target, propKey);
    },
    set(target, propKey, value) {

    }
});

console.log(px.age);
console.log(px.money);

上面的示例控制台总共输出四行内容:

// 调用 age 时,get 函数中 console 执行的输出
{name: "小明", age: 18, money: 100} "age"  
// 输出的 px.age 的值  
undefined
// 调用 money 时,get 函数中 console 执行的输出
{name: "小明", age: 18, money: 100} "money"
// 输出的 money 的值
undefined

通过这个例子,我们可以看出:

  • new Proxy(obj, handle) 是创建了一个新的 Proxy 实例对象,该对象完全深拷贝了构造函数的参数 obj;
  • 该实例和构造函数的参数 obj 并不等价;
  • 所谓的拦截,其实是对 px 这个对象的属性的拦截,对于原数据 boy 并没有任何影响;
  • 当获取实例 px 的属性值时,get 函数的执行在属性值的获取之前,也就实现了拦截的功能;
  • 上面的例子中获取到的 px.agepx.money 都是 undefined 是因为获取数据时被 get 函数拦截了,get 函数返回什么,px 对象的属性值就是什么;

想要获取 Prxoy 实例的属性值

let boy = {
    name: '小明',
    age: 18,
    money: 100
};
let px = new Proxy(boy,{
    get(target, propKey) {
        switch(propKey) {
            case 'name':
                return target.name; // 直接返回原数据的值
                break;
            case 'age':
                return 18; // 返回一个固定的值
                break;
            case 'money':
                // 根据原数据的值的不同情况,返回对应的值(过滤/判断)
                if(target.money <= 100) {
                    return 0;                
                } else return target.money;
                break;
        }
    },
    set(target, propKey, value) {

    }
});

console.log(px.name); // '小明'
console.log(px.age); // 18
console.log(px.money); // 0

设置 Proxy 实例的一个属性值

get 一样,想要在设置值的时候对该操作进行拦截,设置值的动作要对 Proxy 实例,并不是原对象。

let boy = {
    name: '小明',
    age: 18,
    money: 100
};
let px = new Proxy(boy,{
    get(target, propKey) {
       return target[propKey];
    },
    set(target, propKey, value) {
        console.log(target, propKey, value);
    }
});
px.name = '小红';
px.age = 20;
px.money = 200;

console.log(boy);

上面代码在控制台的输出为

{name: "小明", age: 18, money: 100} "name" "小红"
{name: "小明", age: 18, money: 100} "age" 20
{name: "小明", age: 18, money: 100} "money" 200

{name: "小明", age: 18, money: 100} // boy 的值

我们可以看到:

  • 每次对 px 的任意属性做操作,都会出发 set 函数;
  • 如果 set 函数中不做任何操作,boy 的值就不会改变;

所以如果要改变 boy 的值,需要在 set 中单独操作设置:

let boy = {
    name: '小明',
    age: 18,
    money: 100
};
let px = new Proxy(boy,{
    get(target, propKey) {
       return target[propKey];
    },
    set(target, propKey, value) {
        // 不同的 propKey 的设置有不同的操作约束
        switch(propKey) {
            case 'age':
                if(value > 30) value = 13;
                break;
            case 'money':
                value *= 2;
                break;
        }
        target[propKey] = value;
    }
});
px.name = '小红';
px.age = 20;
px.money = 200;

需要注意的是,如果我们不做拦截约束,那么对于 Proxy 实例的操作会直接追朔到原数据。
示范:

let boy = {
    name: '小明',
    age: 18,
    money: 100
};
let px = new Proxy(boy,{});

// 没有设置 set 函数和 get 函数,对于 px 的操作会直接响应到原对象 boy
px.name = '小红';
px.age = 20;
px.money = 200;

根据上面的示例,我们可以了解 Proxy 的基本概念:

Proxy 是个类,我们从其中实例化对象,用来将代理指定对象属性值的操作,Proxy 构造函数接收两个参数,第一参数是需要代理的对象,第二个参数是代理约束设置项。
模式是:

let px = new Proxy(obj, handler);

参数:

  • obj: 需要 Proxy 代理操作的原数据;
  • handle: Proxy 代理操作配置对象;

常用的 Proxy 支持的拦截操作

所有拦截都有的参数:

  • target: 原数据;
  • key: 要操作的键;
  • val: 要操作的 key 键对应的值;
  • receiver: Proxy 实例本身;

1. get(target, key, receiver); 获取键值的拦截操作;

我们除了可以在获取值时做过滤及约束,我们还可以在这里抛出异常,比如尝试获取一个不存在于对象中的实例;

let px = new Proxy({
    name: '小明',
    age: 20
}, {
    get(target, key, receiver) {
        if(key in target) {
            return target[key];
        } else {
            throw new ReferenceError('Prop ' + key + ' is undefined');
        }
    }
});

2. set(target, key, val, receiver); 设置键值的拦截操作;

3. has(target, key); 拦截 HasProperty 操作,就是判断某个对象中是否存在制定属性,比如 in 运算符;

let px = new Proxy({
    age: 20
}, {
    has(target, key) {
        if(key in target) return true;
        else {
            if(key === 'name') {
                target.name = null;
                return true;
            } else return false;
        }        
    }
});
console.log('age' in px); // true
console.log('money' in px); // false
console.log('name' in px); // true 

4. apply(target, ctx, args); 拦截对于函数的调用,包括 call 和 apply 操作;

参数:

  • target: 原函数;
  • ctx: 调用函数的上下文环境;
  • args: 调用函数时传的参数;
示例
function fn(x, y) {
    return x + y;
};

let px = new Proxy(fn, {
    apply(target, ctx, args) {
        console.log(target); // f fn() {...}
        // 当函数为全局函数时,上下文环境的值是 undefined
        console.log(ctx); // undefined
        console.log(args); // [190, 456]
        return args[0] + args[1] + 100;
    }
});

const res = px(190, 456);
// 这里返回的是 Proxy 实例的 apply 函数的返回值
console.log(res); // 746 
关于上下文环境
let MyMath = {
    sum(x, y) {
        return x + y;
    }
};

let pxTable = {
    px: new Proxy(MyMath.sum, {
        apply(target, ctx, args) {
            console.log(target); // f sum() {}
            console.log(ctx); // pxTable
            console.log(args); // [190]
            return args[0] + args[1] + 100;
        }
    })
};

const res = pxTable.px(190, 456);
console.log(res); // 746

注意: Proxy 代理中 apply 函数的第二参数,上下文环境指的是调用 Proxy 时的上下文环境,而非原数据的上下文环境。

对于 call 和 apply 的拦截
let obj = {
    a: 34,
    b: 45
};

function fn() {
    return this.a + this.b;
}

let px = new Proxy(fn, {
    apply(target, ctx, args) {
        console.log(target); // f fn() {}
        console.log(ctx); // obj
        console.log(args); //[89, 7]
        return ctx.a + ctx.b;
    }
});

const res = px.call(obj, 89, 7);
console.log(res); // 79

5. construct(target, args, newTarget);

参数:

  • target:目标对象;
  • args: 构造函数的参数数组;
  • newTarget: 创造实例对象时,new命令作用的构造函数(下面例子的p);
function Person(name, age) {
    this.name = name;
    this.age = age;
}

let px = new Proxy(Person, {
    construct(target, args, newTarget) {
        console.log(target); // Person 函数
        console.log(args); // ['小明', 18]
        console.log(newTarget); // px 实例
        // 可以在这个位置对 new 动作进行拦截
        return new target(...args);
    }
});

const p = new px('小明', 18);
console.log(p); // Person {name: "小明", age: 18}

扫码拉你进入前端技术交流群

在这里插入图片描述

你可能感兴趣的:(JS基础,proxy,javascript)