JavaScript|JavaScript 的 Proxy详解
零、引言
参考文献
- 计算姬 的博客文章:《JavaScript 的 Proxy详解》
- 阮一峰的《ECMAScript 6 入门》- 15.Proxy
问题的起源 vue3.0 开始 Proxy代替Object.defineProperty,产生了一些列疑惑。
- Proxy是什么?
- Proxy能干什么?
- Vue用Object.defineProperty干了什么?
- 为什么用Proxy代替Object.defineProperty?
1.1 理解Proxy MDN定义:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
通俗的讲Proxy是一个对象操作的拦截器,拦截对目标对象的操作,进行一些自定义的行为,一种分层的思想有点类似spring的AOP。
1.2 Proxy怎么用
let p = new Proxy(target, handler);
语法非常简单,只有两个参数,很好理解
target 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
无操作转发代理 代理对象p会将所有应用到它的操作转发到目标对象target上,可以直接对代理对象进行操作,操作会转发到目标对象上。
let target = {};
let p = new Proxy(target, {});
p.a = 37;
// 操作转发到目标console.log(target.a);
// 37. 操作已经被正确地转发
1.3 可以拦截的操作 ? 一共有 13 种可代理操作,每种操作的代号(属性名/方法名)和触发这种操作的方式列举如下。注意,如果没有定义某种操作,那么这种操作会被转发到目标对象身上。
- handler.getPrototypeOf()
- handler.setPrototypeOf()
- handler.isExtensible()
- handler.preventExtensions()
- handler.getOwnPropertyDescriptor()
- handler.defineProperty()
- handler.has()
- handler.get()
- handler.set()
- handler.deleteProperty()
- handler.ownKeys()
- handler.apply()
- handler.construct()
1.4 this的指向 需要特别注意代理对象和拦截操作中的this指向问题
一旦对象被代理之后,他的this就指向了代理对象
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m()// true。目标函数的this指向proxy
handler定义的拦截操作中this指向handler,receiver指向的才是proxy
const target = {
m: 100
};
const handler = {
get(target, property, receiver) {
console.log(this === handler) // true。 拦截操作中的this指向 handler
console.log(receiver === proxy) // true。receiver 指向 代理对象proxy
return target[property]
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.m)
二、Proxy能干什么?
Proxy 各种常用的拦截都能干什么,常用的四种操作
2.1 get() get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身。所有的属性调用都会进入同一个get即使是没有的属性。
- 可以创造一些本来没有的属性
- 可以在取数据时对数据进行验证和转换
- 可以自定义一些语法糖操作
- get返回一个函数的话可以把一个属性转换成方法
var base = {
a: 100,
small: "hello world!!"
};
var proxy = new Proxy(base, {
get(target, property, receiver) {
//属性转换成函数
if ("fn" === property) {
return function(value) {
console.log(value);
};
}if ("ghost" === property) {
return "ghost";
}if ("fn" === property) {
return function(value) {
console.log(value);
};
}
//自定义语法糖
if (property.includes("_")) {
const direct = property.split("_")[1];
const propertyBase = property.split("_")[0];
switch (direct) {
case "Big":
return receiver[propertyBase].toLocaleUpperCase();
default:
break;
}
}if (!(property in target)) {
throw new ReferenceError('Property "' + property + '" does not exist.');
//验证属性值
}return target[property];
}
});
console.log(proxy.a)//输出100正常访问
proxy.fn("fn")//输出fn属性转换为方法
console.log(proxy.small_Big)//输出 HELLO WORLD!!自定义语法糖
console.log(proxy.ghost)//输出ghost创建属性
console.log(proxy.ghost_Big)//输出GHOSTreceiver的使用
console.log(proxy.b)//数据验证抛出错误
2.2 set() set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
- 可以用来验证属性是否符合要求
- 可以用来更改数据格式
- 可以用来监听数据更改事件
- 屏蔽一些赋值操作比如以"_"开头的私有变量
var base = {
a: 100,
small: "hello world!!"
};
const A_Change_Event = "aChangeEvent";
window.addEventListener(A_Change_Event, e => {
console.log(e.data.value);
});
var proxy = new Proxy(base, {
set(obj, property, value, receiver) {
if ("a" === property) {
if (!Number.isInteger(value)) {
throw new TypeError("The" + property + "is not an integer");
}
if (value > 100) {
throw new RangeError("The " + property + " seems invalid");
}obj[property] = value;
//事件
const dataChangeEvent = new Event(A_Change_Event);
//创建一个事件
dataChangeEvent.data = https://www.it610.com/article/{ value };
window.dispatchEvent(dataChangeEvent);
}
}
});
proxy.a = 80;
console.log(proxy.a);
proxy.a = 120;
2.3 apply() 函数对象也可以作为代理的目标对象
apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
最简单的例子
var target = function () { return 'I am the target';
};
var handler = {
apply: function () {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p()
// "I am the proxy"
2.4 construct () construct 拦截的是new操作
接受三个参数
- target:目标对象
- args:构造函数的参数对象
- newTarget:创造实例对象时,new命令作用的构造函数
3.1 Object.defineProperty 实现observe 递归遍历所有属性,使用Object.defineProperty挨个定义监听
var data = https://www.it610.com/article/{name:'kindeng'};
observe(data);
data.name = 'dmq';
// 哈哈哈,监听到值变化了 kindeng --> dmqfunction observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
function defineReactive(data, key, val) {
observe(val);
// 监听子属性
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
return val;
},
set: function(newVal) {
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
}
});
}
3.2 Proxy实现observe 不用提前挨个遍历绑定绑定。每次根据key来判断
observe(data) {
const that = this;
let handler = {
get(target, property) {
return target[property];
},
set(target, key, value) {
let res = Reflect.set(target, key, value);
that.subscribe[key].map(item => {
item.update();
});
return res;
}
}
this.$data = https://www.it610.com/article/new Proxy(data, handler);
}
四、为什么用Proxy代替Object.defineProperty?
? vue3.0使用了Proxy替换了原先遍历对象使用Object.defineProperty方法给属性添加set,get访问器的笨拙做法。也就是说不应遍历了,而是直接监控data对象了。
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量