一文带你搞懂代理Proxy的基本使用

484 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

一、代理Proxy

1.1 什么是代理

通俗理解就是:比如说我们进入公司时,公司都有门禁,我们进入公司时,需要刷我们的工卡,当我们出去的时候,我们需要按一下门禁才能出去,我们在进入和出去的时候,分别进行了刷卡和按开关的操作,这一切都发生在门禁的基础之下。我们可以理解为门禁就是代理。

在代码层面,比如说一个对象内的一个属性,当我们读或写的时候,我们可以用代理来过滤我们的操作。并不是所有的人都有读的权限,也不是所有的写入都合情合理。

代理的目的就是为其它对象提供一种代理来控制对这个对象的访问。

1.2 Object.defineProperty()

Object.defineProperty()会直接在对象上定义一个新的属性,或者修改对象的现有属性,并返回此对象。ES5中,我们可以把它看做一个代理,来过滤拦截我们对对象的操作。下面得代码我们就实现了拦截的set和get方法。但是,下面得代码却存在一个问题,那就是栈溢出。set方法调用时,会出现死循环。

let obj = {}

Object.defineProperty(obj, 'name', {
    get() {
        return 'hello'
    },
    set(val) {
        this.name = val
    }
})

console.log(obj.name) //hello
obj.name = 'Hi'
console.log(obj.name)

遇上上述情况,我们可以通过设置一个新的变量就可以。这是很多新手容易犯的错误。

let obj = {}
let newVal;
Object.defineProperty(obj, 'name', {
    get() {
        return newVal
    },
    set(val) {
        newVal = val
    }
})
obj.name = 'Hi'
console.log(obj.name) //Hi

1.3 Proxy基本写法

new Proxy()的第一个参数就是我们想要代理或者包装的对象,第二个参数{}内可以是一些拦截操作的钩子函数。

let obj = {}

let p = new Proxy(obj,{})

obj.name = "张三"

console.log(obj.name) //张三

1.4 Proxy设置get钩子

可以从如下代码中看出。get的第一个参数target就是我们传入的数组,第二个参数prop对应的就是我们传入的数组下标。当我们想要获取数组的值得时候,会自动的调用get方法,实现过滤拦截

let arr = [1,2,3]

arr = new Proxy(arr, {
    get(target,prop) {
        console.log(target,prop) // [1,2,3]     '1'
        return prop in target ? target[prop] : 'error'
    }
})

console.log(arr[1]) //2

1.5 Proxy设置set钩子

首先,当我们每次往数组中设置值时,都会调用set方法,同时拦截非number类型的设置。 其次,我们还要知道,虽然数组被代理控制,但是数组上原来就有的方法是不受任何影响的

let arr = []

arr = new Proxy(arr, {
    set(target,prop,val) {
        if(typeof val === 'number') {
            target[prop] = val 
            return true
        }else {
            return fasle
        }
    }
})

arr.push(1)
console.log(arr[0]) //1

1.6 Proxy设置has钩子

我们可以利用has钩子来判断元素的有无。当然我们这种例子都是为了简单的学会应用。

let length = {
    min: 1,
    max: 10
}

length = new Proxy(length, {
    has(target,prop) {
        return prop < target.max && prop > target.min
    }
})

console.log(5 in length); //true
console.log(0 in length); //false

1.7 Proxy设置ownkeys钩子

ownkeys用于对象循环遍历的时候进行拦截。下面得代码拦截以_开头的属性

let user = {
    name: '张三',
    age: 20,
    _sex: 'male'
}

user = new Proxy(user, {
    ownKeys(target) {
        return Object.keys(target).filter( key => !key.startsWith('_'))
    }
})

for(let item in user) {
    console.log(item) //name age
}

1.8 Proxy设置delProperty钩子

用来拦截判断该属性是否可以被删除

let user = {
    name: '张三',
    age: 20,
    _sex: 'male'
}

user = new Proxy(user, {
    delProperty(target,prop) {
        if(prop.satrtWith('_')) {
            throw new Error('这个属性不能删除')
        }else {
            delete target[prop]
            return true
        }
    }
})

delete user.age

1.9 Proxy设置apply拦截函数的调用

Proxy同样可以代理函数,操作返回的结果。apply可以拦截函数的调用。apple的第一个参数就是sum函数,第二个参数是函数的上下文,第三个参数是我们需要操作的值

// delete user.age
let all = 0
let sum = (...args) => {
    args.forEach(item => {
        all+=item
    })
    return all
}

sum = new Proxy(sum, {
    apply(target,ctx,args) {
        return target(...args) * 2
    }
})

console.log(sum(1,2,3)); //12
console.log(sum.call(null,1,2,3));
console.log(sum.apply(null,[1,2,3]));

1.10 Proxy设置construct拦截new操作

construct的第一个参数是People本身,第二个参数是new People传入的参数,第三个参数是实例对象

class People {
    constructor(name,age) {
        this.name = name
        this.age = age
    }
}

People = new Proxy(People, {
    construct(target,args,newVal) {
        return new target(...args)
    }
})

console.log(new People('张三'));