Proxy

Proxy 用于修改某些操作的默认行为,等同与在语言层面做出修改,属于一种 meta programming。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = new Proxy(
{},
{
get: function (target, key, receiver) {
console.log(`getting ${key}`)
return Reflect.get(target, key, receiver)
},
set: function (target, key, receiver) {
console.log(`setting ${key}`)
return Reflect.set(target, key, receiver)
},
}
)
obj.count = 1
//setting count
obj.count
//getting count

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

1
let proxy = new Proxy(target, handler)

将 Proxy 对象设置到 object.proxy 属性,从而可以在 object 对象上调用:

1
let obj = { proxy: new Proxy(target, handler) }

Proxy 实例也可以作为其他对象的原型对象:

1
2
3
4
5
6
7
8
9
10
let proxy = new Proxy(
{},
{
get: function (target, handler) {
return 2
},
}
)
let obj = Object.create(proxy)
obj.time //2

同一个 Proxy 可以设置多个拦截属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let handler = {
get: function (target, name) {
if (name === "prototype") {
return Object.prototype
}
return "Hello, " + name
},
apply: function (target, thisBinding, args) {
return args[0]
},
construct: function (target, args) {
return { value: args[1] }
},
}
let fproxy = new Proxy(function (x, y) {
return x + y
}, handler)

fproxy(1, 2) //1
new fproxy(1, 2) //2
fproxy.prototype === Object.prototype //true
fproxy.foo //'hello, foo'

Proxy 方法

  • get(target, propKey, receiver)
    拦截对象的属性读取,如 proxy.foo 和 proxy[‘foo’]。最后一个参数是可选的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let person = {
    name: "张三",
    }
    let proxy = new Proxy(person, {
    get: function (target, property) {
    if (property in target) {
    return target[property]
    } else {
    throw new ReferenceError('Property "' + property + '" does not exist.')
    }
    },
    })
    proxy.name //张三
    proxy.age //ReferenceError
    get 方法可以继承:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let proto = new Proxy(
    {},
    {
    get(target, propertyKey, receiver) {
    console.log("get " + propertyKey)
    return target[propertyKey]
    },
    }
    )
    let obj = Object.create(proto)
    obj.aaa //get aaa
    使用 get 实现负数索引:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function createArray(...elements) {
    let handler = {
    get(target, propKey, receiver) {
    let index = Number(propKey)
    if (index < 0) {
    propKey = String(target.length + index)
    }
    return Reflect.get(target, propKey, receiver)
    },
    }
    let target = []
    target.push(...elements)
    return new Proxy(target, handler)
    }
    let arr = createArray("a", "b", "c")
    arr[-1] //'c'
    将 get 转为执行某个函数,实现属性的链式操作:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var pipe = (function () {
    return function (value) {
    var funcStack = [];
    var oproxy = new Proxy({}, {
    get : function (pipeObject, fnName) {
    if (fnName === 'get') {
    return funcStack.reduce(function (val, fn) { //reduce 接受一个函数作为累加器,从左到右缩减,最终计算为一个值
    return fn(val);
    }, value);
    }
    funcStack.push(window[fnName]);
    return oproxy;
    }
    });
    return oproxy;
    }
    }());
    var double = n => n _ 2;
    var pow = n => n _ n;
    var reverseInt = n => n.toString().split('').reverse().join('') | 0;
    pipe(3).double.pow.reverseInt.get; //63
  • set(target, propKey, value, receiver)
    拦截对象的属性设置,如 proxy.foo = 1,返回一个布尔值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let validator = {
    set: function (obj, prop, value) {
    if (prop === "age") {
    if (!Number.isInteger(value)) {
    throw new TypeError("The age is not an integer")
    }
    if (value > 200) {
    throw new RangeError("The age seems invalid")
    }
    }
    //age < 200 直接保存
    obj[prop] = value
    },
    }
    let person = new Proxy({}, validator)
    person.age = 100
    person.age = 201 //RangeError
    person.age = "tom" //TypeError
    给对象设置内部属性:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var handler = {
    get(target, key) {
    invariant(key, "get")
    return target[key]
    },
    set(target, key, value) {
    invariant(key, "set")
    target[key] = value
    return true
    },
    }
    function invariant(key, action) {
    if (key[0] === "_") {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`)
    }
    }
    var target = {}
    var proxy = new Proxy(target, handler)
    proxy._temp //Error
    proxy._temp = 2 //Error
  • has(target, propKey)
    拦截 propKey in proxy 的操作,返回一个布尔值。has 拦截对 for…in 循环不生效
  • deleteProperty(target, propKey)
    拦截 delete proxy[propKey]的操作,返回一个布尔值。如果这个方法抛出错误或者返回 false,当前属性就不能被 delete 删除。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var handler = {
    deleteProperty(target, key) {
    invariant(key, "delete")
    return true
    },
    }
    function invariant(key, action) {
    if (key[0] === "_") {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`)
    }
    }
    var target = { _temp: "test" }
    var proxy = new Proxy(target, handler)
    delete proxy._temp //Error
  • ownKeys(target)
    拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回所有属性名,而
    Object.keys()返回结果仅包括目标对象和自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey)
    拦截 Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象或者 undefined。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var handler = {
    getOwnPropertyDescriptor(target, key) {
    if (key[0] === "_") {
    return
    }
    return Object.getOwnPropertyDescriptor(target, key)
    },
    }
    var target = { _foo: "foo", bar: "bar" }
    var proxy = new Proxy(target, handler)
    Object.getOwnPropertyDescriptor(proxy, "_foo") //undefined
    Object.getOwnPropertyDescriptor(proxy, "bar") //{ value : 'bar', writable : true, enumerable : true, configurable : true }
  • defineProperty(target, propKey, propDesc)
    拦截 Object.defineProperty:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var handler = {
    defineProperty(target, key, descriptor) {
    return false
    },
    }
    var target = {}
    var proxy = new Proxy(target, handler)
    proxy.foo = "bar"
    proxy.foo //undefined
  • preventExtensions(target)
    拦截 Object.preventExtensions,必须返回布尔值,否则会强制转换为布尔值。
  • getPrototypeOf(target)
    拦截获取对象原型
  • isExtensible(target)
    拦截 Object.isExtensible 操作
  • setPrototypeOf(target, proto)
    拦截 Object.setPrototypeOf 方法
  • apply(target, object, args)
    拦截函数的调用、call 和 apply 操作,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var target = function () {
    return "test"
    }
    var handler = {
    apply: function () {
    return "apply"
    },
    }
    let p = new Proxy(target, handler)
    p() //apply
  • construct(target, args)
    拦截 new 命令,返回的必须是对象,否则会报错:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var p = new Proxy(function () {}, {
    construct: function (target, args) {
    console.log("called " + args.join(","))
    return { value: args[0] * 10 }
    },
    })
    new p(1).value
    //called 1
    //10

Proxy.revocable()

返回一个可取消的 Proxy 实例:

1
2
3
4
5
6
7
8
let target = {}
let handler = {}
let { proxy, revoke } = Proxy.revocable(target, handler)
proxy.foo = 123
proxy.foo //123

revoke()
proxy.foo //TypeError

执行 revoke 函数后再访问 Proxy 实例,就会抛出一个错误。

this 问题

在 Proxy 代理下,目标对象内部的 this 关键字会指向 Proxy 代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
const _name = new WeakMap()
class Person {
constructor(name) {
_name.set(this, name)
}
get(name) {
return _name.get(this)
}
}
const jane = new Person("Jane")
jane.name //'Jane'
const proxy = new Proxy(jane, {})
proxy.name //undefined

此外,有些原生对象内部属性只有通过正确的 this 才能获取,所以 Proxy 也无法代理这些原生对象的属性

1
2
3
4
const target = new Date()
const handler = {}
const proxy = new Proxy(target, handler)
proxy.getDate() //TypeError: This is not a Date Object

这时,this 绑定原始对象就可以解决

1
2
3
4
5
6
7
8
9
10
11
const target = new Date("2018-8-31")
const handler = {
get(target, prop) {
if (prop === "getDate") {
return target.getDate.bind(target)
}
return Reflect.get(target, prop)
},
}
const proxy = new Proxy(target, handler)
proxy.getDate() //31

观察者模式

Observer mode:函数自动观察对象,一旦数据有变化,函数就会自动执行。

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
let hero = {
// data object
name: "test",
hp: 100,
sp: 100,
equipment: ["weapon"],
}
const observableArray = new Set()

const handler = {
set: (target, key, value, receiver) => {
const result = Reflect.set(target, key, value, receiver)
observableArray.forEach((item) => item(key))

return result
},
}

/**
* Create a Proxy object
* @param {Object} obj the data object
* @returns {Object} Proxy
*/
const createProxy = (obj) => new Proxy(obj, handler)
const heroProxy = createProxy(hero) // the data Proxy

observableArray.add((prop) => {
// callback function
console.log(`new ${prop}: ${heroProxy[prop]}`) // log value of the prop handled
})

heroProxy.name = "change name" // new name: change name
heroProxy.sp = 12 // new sp: 12