浅谈ES6中的Proxy

1.什么是Proxy

MDN中的描述

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Proxy 对象一般用于给基本操作定义自定义行为(例如:属性查询,赋值,枚举,函数调用等)

Proxy是ES6中原生提供的一个构造函数,Proxy字面意思是“代理”,其实它更像一种拦截器,在访问,赋值等基本操作时会先到我们定义好的拦截方法中,根据访问的信息去执行我们想要的操作。


2.Proxy的用法

Proxy构造函数中接受两个参数

1
new Proxy(target, handler);
  • target 参数指的是目标对象
  • handler 指用户自定义的行为对象

来看一个使用例子🌰:

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
var handler = {
get (target, propkey, receiver) {
console.log('getting values');
return target[propkey] || 'value is not defined';
},
set (target, propkey, value, receiver) {
console.log('setting values');
return target[propkey] = value;
}
}
var proxy = new Proxy({}, handler);

console.log(proxy.a);
// 输出
// getting values
// value is not defined

proxy.a = 111;
// 输出
// setting values

console.log(proxy.a)
// 输出
// getting values
// 111

上面代码定义了一个拥有 get 和 set 的代理,当我们在访问proxy对象中的a时,会进入handler中的 get 方法并执行。同样,当我们给proxy赋值时,亦会进入handler中的set方法中。

  • proxy中的的get和set方法和Object.defineProperty的descriptor的get和set方法很像,但proxy中的get和set更为强大,不仅可以监听数组下标的变化,还可以监听到对象原型属性的变化

    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
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
        /**
    * proxy监听数组下标
    */
    var proxyArr = new Proxy([], {
    get(target, propkey) {
    console.log('数组下标被监听:get');
    return target[propkey];
    },
    set(target, propkey, value) {
    console.log('数组下标被监听:set');
    return target[propkey] = value;
    }
    })
    console.log(proxyArr[0]);
    // 数组下标被监听:get
    // undefined

    proxyArr[0] = 1;
    // 数组下标被监听:set

    /**
    * proxy监听对象原型
    */
    var obj = {a:1};
    var prototypeObj = Object.create(obj);
    var proxyPrototype = new Proxy(prototypeObj, {
    get(target, propkey) {
    console.log('对象原型被监听:get');
    return target[propkey];
    },
    set(target, propkey, value) {
    console.log('对象原型被监听:set');
    return target[propkey] = value;
    }
    })
    console.log(proxyPrototype.a);
    // 对象原型被监听:get
    // 1

    proxyPrototype.a = 2;
    // 对象原型被监听:set
    ```

    * `handler`中的对象属性
    `Proxy`不仅可以用于监听数据变化,还可以监听调用函数,构造函数实例化等操作
    `handler`对象具体的参数有13个:
    * `get(target, propkey, receiver)`
    * `set(target, propkey, receiver)`
    * `has(target, propkey)`
    * `deleteProperty(target, propkey)`
    * `ownkeys(target)`
    * `getOwnPropertyDescriptor(target, propKey)`
    * `defineProperty(target, propkey, propDesc)`
    * `preventExtensions(target)`
    * `getPrototypeOf(target)`
    * `isExtensible(target)`
    * `setPrototypeOf(target, proto)`
    * `apply(target, object, args) // 调用函数前触发`
    * `construct(target, args) // 构造函数实例化前触发`

    ---

    ## 3.Proxy与defineProperty比较

    1. 上文说到,`defineProperty`不能监听数组下标变化和对象原型的变化,`Proxy`则可以支持。

    2. `defineProperty`监听的是一个对象的属性,`proxy`监听的是整个对象。

    3.`defineProperty`比较`proxy`的速度更快,我们写两个测试的用例比较一下两者的速度
    ``` javascript
    /**
    * defineProperty测试用例
    */
    var defineObj = {};
    console.time('defineProperty');
    for (var x = 0; x < 100000; x++) {
    Object.defineProperty(defineObj, 'test_' + x, {
    get() {
    return value;
    },
    set(value) {
    return defineObj['test_' + x] = value;
    }
    });
    }
    console.timeEnd('defineProperty');

    /**
    * proxy测试用例
    */
    var proxy = new Proxy({}, {
    get(target, propkey) {
    return target[propkey];
    },

    set(target, propkey, value) {
    return target[propkey] = value
    }
    });
    console.time('proxy');
    for (var x = 0; x < 100000; x++) {
    proxy['test_' + x] = 1;
    }
    console.timeEnd('proxy');
    ```
    * 在chrome中运行proxy的速度
    ![](https://user-gold-cdn.xitu.io/2019/11/5/16e3af5938767e98?w=266&h=221&f=jpeg&s=25211)

    * 运行defineProperty的速度

    ![](https://user-gold-cdn.xitu.io/2019/11/5/16e3af7c9f6dda36?w=408&h=194&f=jpeg&s=25797)


    4. 不仅是运行速度的快了,`proxy``defineProperty`占用的内存更少,我们用上面的例子改造测试一下两者的内存占用
    ``` javascript
    /**
    * proxy 内存占用测试
    */
    var i = 0;
    var proxy = new Proxy({}, {
    get(target, propkey) {
    return target[propkey];
    },

    set(target, propkey, value) {
    return target[propkey] = value
    }
    });
    console.log('start');
    var timer = null;
    timer = setInterval(function(){
    if (i > 10) {
    console.log('finish');
    return clearTimeout(timer);
    }
    i++;
    for (var x = 0; x < 10000; x++) {
    proxy['test_' + i + '_' + x] = 1;
    }
    }, 1000)


    /**
    * defineProperty 内存占用测试
    */
    var i = 0;
    var defineObj = {};
    console.log('start');
    timer = setInterval(function(){
    if (i > 10) {
    console.log('finish');
    return clearTimeout(timer);
    }
    for (var x = 0; x < 10000; x++) {
    Object.defineProperty(defineObj, 'test_' + i + '_' + x,{
    get() {
    return value;
    },
    set(value) {
    return defineObj['test_' + i + '_' + x] = value;
    }
    });
    }
    i++;
    }, 1000)
  • chrome 运行defineProperty的测试用例

  • 运行proxy的测试用例

4.最后


本节我们了解了Proxy中的作用和与defineProperty相比,Proxy的优势。
下一节就来实践一下,使用proxy实现一个双向绑定

如有不严谨或者错误的地方,望大佬们指出~