Proxy

本文最后更新于:2023年12月5日 晚上

MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

const p = new Proxy(target, handler)
  • target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数、甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为。

方法

Proxy.revocable()

创建一个可撤销的Proxy对象。

handler对象的方法

handler对象是一个容纳一批特定属性的占位符对象。它包含有Proxy的各个捕获器(trap)。

所有的捕获器是可选的。如果没有定义某个捕获器,那么就会保留源对象的默认行为。

getPrototypeOf()

handler.getPrototypeOf() 是一个代理(Proxy)方法,当读取代理对象的原型时,该方法就会被调用。

const monster1 = {
  eyeCount: 4
};

const monsterPrototype = {
  eyeCount: 2
};

const handler = {
  getPrototypeOf(target) {
    return monsterPrototype;
  }
};

const proxy1 = new Proxy(monster1, handler);

console.log(Object.getPrototypeOf(proxy1) === monsterPrototype);
// Expected output: true

console.log(Object.getPrototypeOf(proxy1).eyeCount);
// Expected output: 2

setPrototypeOf()

handler.setPrototypeOf() 方法主要用来拦截 Object.setPrototypeOf().

const p = new Proxy(target, {
  setPrototypeOf: function(target, prototype) {
  }
});

isExtensible()

handler.isExtensible() 方法用于拦截对对象的 Object.isExtensible()。

const monster1 = {
  canEvolve: true
};

const handler1 = {
  isExtensible(target) {
    return Reflect.isExtensible(target);
  },
  preventExtensions(target) {
    target.canEvolve = false;
    return Reflect.preventExtensions(target);
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(Object.isExtensible(proxy1));    // true

console.log(monster1.canEvolve);      // true

Object.preventExtensions(proxy1);      // true

console.log(Object.isExtensible(proxy1));    // true

console.log(monster1.canEvolve);      // true

preventExtensions()

handler.preventExtensions() 方法用于设置对Object.preventExtensions()的拦截。

Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

var p = new Proxy(target, {
  preventExtensions: function(target) {
  }
});
const monster1 = {
  canEvolve: true
};

const handler1 = {
  preventExtensions(target) {
    target.canEvolve = false;
    Object.preventExtensions(target);
    return true;
  }
};

const proxy1 = new Proxy(monster1, handler1);

console.log(monster1.canEvolve);    // true

Object.preventExtensions(proxy1);

console.log(monster1.canEvolve);    // fasle

getOwnPropertyDescriptor()

handler.getOwnPropertyDescriptor() 方法是 Object.getOwnPropertyDescriptor() 的钩子。

var p = new Proxy(target, {
  getOwnPropertyDescriptor: function(target, prop) {
  }
});
const p = new Proxy({ a: 20 }, {
    getOwnPropertyDescriptor(target, prop) {
        console.log('called: ' + prop)
        return { configurable: true, enumerable: true, value: 10 }
    }
})

console.log(Object.getOwnPropertyDescriptor(p, 'a').value)
// called: a
// 10

defineProperty()

handler.defineProperty() 用于拦截对象的 Object.defineProperty() 操作。

vue2的双向绑定就是通过 Object.defineProperty() 实现的。

var p = new Proxy(target, {
  defineProperty: function(target, property, descriptor) {
  }
});

has()

handler.has() 方法是针对 in 操作符的代理方法。

示例,_开头的属性为私有属性,使用in判断的时候返回false

const handler1 = {
  has(target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};

const monster1 = {
  _secret: 'easily scared',
  eyeCount: 4
};

const proxy1 = new Proxy(monster1, handler1);

console.log('eyeCount' in proxy1);     // true

console.log('_secret' in proxy1);     // false

console.log('_secret' in monster1);     // true

get()

handler.get() 方法用于拦截对象的读取属性操作。

var p = new Proxy(target, {
  get: function(target, property, receiver) {
  }
});
const p = new Proxy({ a: 10 }, {
    get(target, prop, receiver) {
        return target[prop] * 2
    }
})

console.log(p.a)   // 20

set()

handler.set() 方法是设置属性值操作的捕获器。

const p = new Proxy(target, {
  set: function(target, property, value, receiver) {
  }
});
const monster1 = { eyeCount: 4 };

const handler1 = {
  set(obj, prop, value) {
    if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
      console.log('必须是偶数');
    } else {
      return Reflect.set(...arguments);
    }
  }
};

const proxy1 = new Proxy(monster1, handler1);

proxy1.eyeCount = 1;    // 必须是偶数

console.log(proxy1.eyeCount);  // 4

proxy1.eyeCount = 2;
console.log(proxy1.eyeCount);  // 2

deleteProperty()

handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作。

var p = new Proxy(target, {
  deleteProperty: function(target, property) {
  }
});

ownKeys()

handler.ownKeys() 方法用于拦截 Reflect.ownKeys().

const monster1 = {
    _age: 111,
    [Symbol('secret')]: 'I am scared!',
    eyeCount: 4
}

const proxy1 = new Proxy(monster1, {
    ownKeys(target) {
        return Reflect.ownKeys(target)
    }
})

for (const key of Object.keys(proxy1)) {
    console.log(key)
    // Expected output: "_age"
    // Expected output: "eyeCount"
}

apply()

handler.apply() 方法用于拦截函数的调用。

const sum = (a, b) => a + b
const handler = {
    apply: (target, _, args) => {
        return target(...args) * 10
    },

}
const proxy = new Proxy(sum, handler)
console.log(sum(1, 2))      // 3
console.log(proxy(1, 2))     // 30

construct()

handler.construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)。

function sum(a, b) {
    return { a, b }
}
const handler = {
    construct: (target, args) => {
        for (let index = 0; index < args.length; index++) {
            args[index] += 1
        }
        return new target(...args)
    }
}
const proxy = new Proxy(sum, handler)
console.log(new proxy(1, 2))                // {a: 2, b: 3}

上例中,sum不能是只能是普通函数,不能是箭头函数,因为箭头函数不能new。


Proxy
http://blog.lujinkai.cn/前端/Vue3/Proxy/
作者
像方便面一样的男子
发布于
2023年2月23日
更新于
2023年12月5日
许可协议