Proxy


上次更新时间:11/2/2020, 8:29:36 PM 0

Proxy 是 es6 新增的内置对象,它用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。

如果传入是数组,则拦截数组操作,如果传入是对象,则拦截对象操作。传入什么,则输出什么,不会像 Set、Map 一样,改变数据结构。

# 创建

语法:new Proxy(target, handler)

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

obj.count = 1; // setting count!

Object.prototype.toString.call(obj) // "[object Object]" 传入什么则输出什么 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 拦截方法

方法 描述 返回值
get(target, propKey, receiver) 拦截对象属性的读取 属性值
set(target, propKey, value, receiver) 拦截对象属性的设置 布尔值
has(target, propKey) 拦截 in 的操作 布尔值
defineProperty(target, propKey, propDesc) 拦截 Object.defineProperty() 布尔值
deleteProperty(target, propKey) 拦截 delete 操作 布尔值
ownKeys(target) 拦截 Object.getOwnPropertyNames() 和 Object.getOwnPropertySymbols() 数组
getOwnPropertyDescriptor(target, propKey) 拦截 Object.getOwnPropertyDescriptor() 属性的描述对象
getPrototypeOf(target) 拦截 Object.getPrototypeOf() 对象
setPrototypeOf(target, proto) 拦截 Object.setPrototypeOf() 布尔值
isExtensible(target) 拦截 Object.isExtensible() 布尔值
preventExtensions(target) 拦截 Object.preventExtensions() 布尔值
apply(target, object, args) 拦截函数调用的操作,obj.apply()、obj.call()
construct(target, args) 拦截 new 实例化操作

# 优缺点

# Proxy 的优势:

  • Proxy 可以直接监听对象而非属性,可以监听新增/删除属性;
  • Proxy 可以直接监听数组的变化;
  • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;

# Proxy 不足:

  • 兼容性问题,而且无法使用 polyfill 抹平

# Object.defineProperty 的优势:

  • 兼容性好,支持 IE9

# Object.defineProperty 不足

  • 无法监听数组的变化
  • 必须遍历对象的每个属性
  • 必须深层遍历嵌套的对象
  • 无法监听新增属性、删除属性
  • 需要在开始时一次性递归所有属性

# 使用场景

  1. 运算符重载,
  2. 对象模拟,
  3. 简洁而灵活的API创建,
  4. 对象变化事件
  5. vue 3.0 的双向绑定

proxy的巧用

# 双向数据绑定

使用 proxy 实现双向数据绑定

<div>
    <h3>数据双向绑定</h3>
    <input type="test" id="input">
    <div>
        输入的内容是:<span id="title"></span> <button type="button" name="button" id="titlebtn">修改input框内容</button>
    </div> 
    <div>
        <button type="button" name="button" id="btn">添加到todolist</button>
    </div>
        </div>
    <h3>todo list</h3>
    <ul id="list">
    </ul>
</div>

<script>
    const obj = {};
    const arr = [];
    const input = document.getElementById("input");
    const title = document.getElementById("title");
    const titlebtn = document.getElementById("titlebtn")
    const list = document.getElementById("list");
    const btn = document.getElementById("btn");

    // 数据双向绑定
    const newObj = new Proxy(obj, {
        get: function(target, key, receiver) {
            return Reflect.get(target, key, receiver);
        },
        set: function(target, key, value, receiver) {
            if (key === "text") {
                input.value = value;
                title.innerHTML = value; // 显示
            }
            return Reflect.set(target, key, value, receiver);
        }
    });

    input.addEventListener("keyup", function(e) {
        newObj.text = e.target.value;
    });

    titlebtn.addEventListener("click", function(e) {
        newObj.text = '改变了'
    });

    // 数组监听
    const Render = {
        init: function(arr) {
            const fragment = document.createDocumentFragment();
            for (let i = 0; i < arr.length; i++) {
                const li = document.createElement("li");
                li.textContent = arr[i];
                fragment.appendChild(li);
            }
            list.appendChild(fragment);
        },
        addList: function(val) {
            const li = document.createElement("li");
            li.textContent = val;
            list.appendChild(li);
        }
    };
    
    const newArr = new Proxy(arr, {
        get: function(target, key, receiver) {
            return Reflect.get(target, key, receiver);
        },
        set: function(target, key, value, receiver) {
            if (key !== "length") {
                Render.addList(value);
            }
            return Reflect.set(target, key, value, receiver);
        }
    });
    // 初始化
    window.onload = function() {
        Render.init(arr);
    };
    btn.addEventListener("click", function() {
        newArr.push(newObj.text);
    });

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
上次更新时间: 11/2/2020, 8:29:36 PM