JS Proxy对象的介绍与实践

835 阅读2分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

最近因为机缘巧合之下,发现了这个早有耳闻的JS原生对象,于是特意花时间去进行了一番了解。

介绍

首先,Proxy是一个用于创建一个指定对象的代理,从而实现针对这个对象进行拦截或重新定义基本操作。使用方式如下:

const proxy = new Proxy(target, handler)

其中,proxy是代理;target是一个需要被代理的对象;handler是一个对象,里面包含定义代理行为逻辑的一系列函数。

Target对象

target 是对象类型,可以是普通对象(甚至是空对象)、数组以及函数等。

Handler的处理函数

开始之前需要说明一点是,这些处理函数都不是必须要实现的,不实现的将维持默认处理的方式。

先说2个最常用的:handler.get()handler.set()。正如字面意思,就是劫持proxy的属性的get和set操作。

var foo = {
  b: 'lol'
}
var handler = {
  set(obj, prop, val) {
    console.log(`set foo.${prop} = ${val}`)
    if (/^p\_/.test(prop) || (prop in obj)) {
      obj[prop] = val
    } else {
      console.log(`failed to set prop '${prop}' to object 'foo'`)
    }
  },
  get(obj, prop) {
    console.log(`get foo.${prop}`)
    if (prop in obj) {
      return obj[prop]
    } else {
      console.log(`prop '${prop}' is undefined in 'foo' and illegal to read`)
    }
  }
}

var proxy = new Proxy(foo, handler);

proxy.a = 1
// output: set foo.a = 1
// output: failed to set prop 'a' to object 'foo'
proxy.b = 'Zzz'
// output: set foo.b = Zzz
proxy.p_a = 100
// output: set foo.p_a = 100

foo.p_a = 200
console.log(proxy.p_a)
// output: get foo.p_a
console.log(foo.a, foo.b, foo.p_a)
// output: undefined "Zzz" 200
console.log(proxy.c)
// output: get foo.c
// output: prop 'c' is undefined in 'foo' and illegal to read

一些相对少用的:

handler.apply(func, thisArg, argumentsList)

劫持函数执行操作

// 代码例子
function sum(a, b) {
  return a + b
}

const handler = {
  apply: function(func, thisArg, argumentsList) {
    console.log(`sum: ${argumentsList}`)
    let sum = 0
    for (let i = 0; i < argumentsList.length; i++) {
      sum = func(sum, argumentsList[i])
    }
    return sum
  }
}

const proxy = new Proxy(sum, handler)

console.log(sum(1, 2))
// output: 3
console.log(proxy(1, 2, 3, 10))
// output: 16

handler.construct(func, argumentsList)

劫持new操作符的执行

function Person(a, b) {
  this.name = a
  this.age = b
}

const handler = {
  id_count: 12340,
  construct: function(func, argumentsList) {
    console.log(`args: ${argumentsList}`)
    const ppl = new func(...argumentsList)
    ppl.id = this.id_count++
    return ppl
  }
}

const proxy = new Proxy(Person, handler)

console.log(new Person('Jack', 2))
// output: {name: "Jack", age: 2}
console.log(new proxy('Ben', 10))
// output: {name: "Ben", age: 10, id: 12340}
console.log(new proxy('David', 8))
// output: {name: "David", age: 8, id: 12341}

handler.defineProperty(obj, prop, descriptor)

劫持属性赋值操作

// 代码例子
var foo = {
  b: 'lol'
}
var handler = {
  defineProperty(obj, prop, descriptor) {
    console.log(`define foo.${prop} = ${descriptor.value}`)
    obj[prop] = descriptor.value
    return true
  }
}

var proxy = new Proxy(foo, handler);

proxy.a = 1
// output: define foo.a = 1
proxy.b = 'Zzz'
// output: define foo.b = Zzz
Object.defineProperty(proxy, 'num', {
  value: 666
})
// output: define foo.num = 666

handler.deleteProperty(obj, prop)

劫持delete操作符的操作

// 代码例子
var foo = {
  b: 'lol'
}
var handler = {
  deleteProperty(obj, prop) {
    console.log(`delete foo.${prop}`)
    delete foo[prop]
    return true
  }
}

var proxy = new Proxy(foo, handler);

delete proxy.b
// output: delete foo.b

console.log(foo)
// output: {}

handler.getOwnPropertyDescriptor(obj, prop)

劫持Object.getOwnPropertyDescriptor()方法的执行

// 代码例子
var foo = {
  b: 'lol'
}
var handler = {
  getOwnPropertyDescriptor(obj, prop) {
    console.log(`getProp foo.${prop}`)
    if (prop in obj) {
        return Object.getOwnPropertyDescriptor(obj, prop)
    } else {
        return { configurable: true, enumerable: false, value: ''}
    }
  }
}

var proxy = new Proxy(foo, handler);

console.log(Object.getOwnPropertyDescriptor(proxy, 'a'))
// output: getProp foo.a
// output: {value: "", writable: false, enumerable: false, configurable: true}
console.log(Object.getOwnPropertyDescriptor(proxy, 'b'))
// output: getProp foo.b
// output: {value: "lol", writable: true, enumerable: true, configurable: true}

handler.getPrototypeOf(obj)

劫持Object.getPrototypeOf()方法的执行

// 代码例子
var foo = {
  b: 'lol'
}
var fooProto = {
  b: 'Zzz'
}
var handler = {
  getPrototypeOf(obj) {
    console.log(`getProto foo`)
    return fooProto
  }
}

var proxy = new Proxy(foo, handler);

console.log(Object.getPrototypeOf(proxy))
// output: getProto foo
// output: {b: "Zzz"}

handler.setPrototypeOf(obj, proto)

劫持Object.setPrototypeOf()方法的执行

// 代码例子
var foo = {
}
var fooProto = {
  b: 'Zzz'
}
var handler = {
  setPrototypeOf(obj, proto) {
    console.log(`setProto foo`)
    Object.setPrototypeOf(obj, proto)
    obj.hasProto = true
    return true
  }
}

var proxy = new Proxy(foo, handler);

Object.setPrototypeOf(proxy, fooProto)
// output: setProto foo
console.log(foo)
// output: {hasProto: true}
console.log(foo.b)
// output: "Zzz"

handler.has(obj, prop)

劫持in操作符的执行

// 代码例子
var foo = {
  __a: 1,
  b: 'lol'
}
var handler = {
  has(obj, prop) {
    console.log(`has foo.${prop}`)
    if (/^__/.test(prop)) {
      return false;
    } else {
      return prop in obj
    }
  }
}

var proxy = new Proxy(foo, handler);

console.log('__a' in proxy)
// output: has foo.__a
// output: false
console.log('b' in proxy)
// output: has foo.b
// output: true
console.log('c' in proxy)
// output: has foo.c
// output: false

handler.isExtensible(obj) 和 handler.preventExtensions(obj)

劫持Object.isExtensible()Object.preventExtensions()方法的执行

// 代码例子
var foo = {
  canExtend: true,
  b: 'lol'
}
var handler = {
  isExtensible(obj) {
    console.log(`isExtensible foo`)
    return Object.isExtensible(obj)
  },
  preventExtensions(obj) {
    console.log(`preventExtensions foo`)
    obj.canExtend = false
    return Object.preventExtensions(obj)
  }
}

var proxy = new Proxy(foo, handler);

console.log(Object.isExtensible(proxy))
// output: isExtensible foo
// output: true
console.log(proxy.canExtend)
// output: true
Object.preventExtensions(proxy)
// output: preventExtensions foo
console.log(Object.isExtensible(proxy))
// output: isExtensible foo
// output: false
console.log(proxy.canExtend)
// output: false

实践

从上面的介绍可以看出,Proxy对象能做很多以前无法想象的事情。

限制&校验

把目标对象封装隐藏起来,对操作行为进行监听和限制,也可以对属性处理进行校验,判断操作、赋值数据和类型是否合法。

例如qiankun微前端框架在构建JS沙箱就利用Proxy

拓展

针对原来的目标对象,进行额外的拓展处理,例如构建出有更多初始数据和方法的对象、在方法调用时赋予更强大的功能等。