ES6 Proxy了解及使用

87 阅读5分钟

官网: ES6MDN

概念

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

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

var proxy = new Proxy(target, handler);

new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

Proxy vs Object.defineProperty()

vue使用Proxy 替代 Object.defineProperty()

下面对比下二者区别

1.Object.defineProperty只能劫持对象的属性,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历,而proxy是直接代理对象,不需要遍历操作

2.Object.defineProperty对新增属性需要手动进行observe,因为defineproperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象,再对新增属性再使用defineproperty进行劫持。而proxy可以直接监听对象属性的添加,vue2.0在给data中的数组或对象新增属性时,需要用vm.$set来保证新增属性的响应

3.Object.defineProperty 监听不到数组变化,也不能对 Map,Set 等数据结构做出监听

注意⚠️: Object.defineProperty IE8以下都不兼容 Proxy IE9以下都不兼容

vue3 Proxy劫持数据操作过程:

  1. 数据劫持
  2. 观察者
  3. 数据解析 {{}} 指令等
  4. diff算法 ->DOM

拦截器API

  • get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = vproxy['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)

图片.png

图是 从vue 3.0 Proxy的使用 搬的

get()

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

let obj = {
    name: "张三"
}

let newObj = new Proxy(obj, {
    get(target, key, receiver) {
        if (key in target) {
            return target[key]
        } else {
            throw new ReferenceError("Prop name \"" + key + "\" does not exist.");
        }
    }
})

newObj.name;  // 张三
newObj.age;
// 报错  Uncaught ReferenceError: Prop name "age" does not exist.

可以看到,这里获取对象数据被proxy代理拦截,在proxy内可以进行自定义修改返回值,这里返回的是 target[key] 真实值,下面的打印结果可以看到具体内容,获取你需要查的值,对象内部没有的值这里定义了报错,你也可以返回其他内容

get的三个参数:

图片.png

set

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

let obj = {
    name: "张三"
}
let newObj = new Proxy(obj, {
    set(target, key, value, receiver) {
        console.log("初始值", target, "\n修改的属性及修改后的值", key, value);
        target[key] = value;
        return true
    }
})
newObj.name = "李四";  // 张三
newObj.age = 200;
console.log(newObj)  // Proxy(Object) {name: '李四', age: 200}

运行结果:

图片.png

has

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

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

let obj = {
    name: "张三"
}
let newObj = new Proxy(obj, {
    has(target, key) {
        return key in target
    }
})

console.log("name" in newObj); // true

deleteProperty

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除 Reflect.deleteProperty() 也可以触发 deleteProperty方法

let obj = {
    name: "张三",
    age: '20'
}
let newObj = new Proxy(obj, {
    deleteProperty(target, key) {
        console.log("deleteProperty", target, key);
        if (!target[key]) {
            throw new ReferenceError("Prop name \"" + key + "\" does not exist.");
        }
        delete target[key]
        return true
    }
})

delete newObj.name
// 等价于
Reflect.deleteProperty(newObj, 'age')

// 不存在的属性直接抛出异常
Reflect.deleteProperty(newObj, 'sex') // 报错  

newObj  // Proxy(Object) {}

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

let obj = {
    name: "张三",
    age: '20'
}

Object.defineProperty(obj, 'name', {
    enumerable: false,
    writable: false,
    configurable: false 
});
let newObj = new Proxy(obj, {
    deleteProperty(target, key) {
        delete target[key]
        return true
    }
})

delete newObj.name  // 报错

运行结果:

图片.png

getOwnPropertyDescriptor

Object.defineProperty()用于设置对象属性

Object.defineProperty(obj, 'name', {
    enumerable: false,
    writable: false,
    configurable: false 
});

获取对象属性:

let obj = {
    name: "张三",
    age: '20'
}
let newObj = new Proxy(obj, {
    getOwnPropertyDescriptor(target, key) {
        return Object.getOwnPropertyDescriptor(target, key);
    }
})

Object.getOwnPropertyDescriptor(newObj, 'name');
// {value: '张三', writable: true, enumerable: true, configurable: true}

ownKeys

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

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in循环
let obj = {
    name: "张三",
    age: '20'
}
let newObj = new Proxy(obj, {
    ownKeys(target) {
        console.log("ownKeys", target);
        return ["1"]
    }
})

Object.getOwnPropertyNames(newObj)
// ["1"]

通过Proxy实现简单双向绑定

实现输入的同时,下方同步展示输入内容

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="text" id="input" placeholder="请输入" />
    <p id="text"></p>
    <script>
        const obj = {}
        const input = document.getElementById("input");
        const text = document.getElementById("text");

        const newObj = new Proxy(obj, {
            set(target, key, value) {
                if (key === "text") {
                    input.value = value
                    text.innerHTML = value
                }
                return Reflect.set(target, key, value)
            },
            get(target, value) {
                return Reflect.get(target, key)
            }
        })

        input.addEventListener('keyup', function (e) {
            newObj.text = e.target.value
        })
    </script>
</body>

</html>

运行结果:

图片.png