proxy写监听方法,实现响应式
var data = { price: 5, quantity: 2 };var data_without_proxy = data; // 保存源对象
data = new Proxy(data_without_proxy, {
// 重写数据以在中间创建一个代理
get(obj, key) {
console.log(obj+'取值')
},
set(obj, key, newVal) {
console.log(obj+"设置值")
}
});
data.price = 8 //设置
data.price //取值
【第1420期】JavaScript 响应式与 Proxy
前言
前端的温度下降了吗?今日早读文章由@李金超推荐,汽车之家@花生翻译分享。
@花生,就职于汽车之家用户产品中心团队,云云搬砖码农的中的一员。
正文从这开始~~
Vue与Proxy
在之前的文章中,我们模拟了Vue的响应式引擎。使用Object.defineProperty()通过getters/setters实现属性的响应性。
如果你一直有关注Vue的发展,就会发现2.x-next版本开始其响应式引擎将使用Proxy重写,这就与我们之前讲的不同了。
1-1
响应式引擎利用代理进行重写——标黄线的(译者注)。
I wanted to ask Evan what exactly this might look like and the advantages we get from it(当成一句玩笑吧).
这样做的好处
Proxy允许我们创建一个对象的虚拟代理(替代对象),并为我们提供了在访问或修改原始对象时,可以进行拦截的处理方法(handler),如set()、get()和deleteProperty()等等。这样我们就可以避免很常见的这两种限制:
添加新的响应性属性要使用Vue.$set(),删除现有的响应性属性要使用Vue.$delete()。
数组的更新检测。
之前的代码
我们之前使用Object.defineProperty()来实现监听属性的访问和修改这两种操作,代码如下:
let data = { price: 5, quantity: 2 }
let target = null
class Dep {
constructor () {
this.subscribers = []
}
depend () {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
Object.keys(data).forEach(key => {
let internalValue = data[key]
const dep = new Dep()
Object.defineProperty(data, key, {
get() {
dep.depend()
return internalValue
},
set(newVal) {
internalValue = newVal
dep.notify()
}
})
})
function watcher(myFun) {
target = myFun
target()
target = null
}
watcher(() => {
data.total = data.price * data.quantity
})
console.log("total = " + data.total)
data.price = 20
console.log("total = " + data.total)
data.quantity = 10
console.log("total = " + data.total)
使用Proxy克服限制
我们可以使用以下方法在data对象上建立一个代理,而不是遍历每个属性来添加getter/setter。
// data 是我们准备要创建代理的源对象
const observedData = new Proxy(data, {
get() {
// 访问源对象属性时调用
},
set() {
// 修改源对象属性时调用
},
deleteProperty() {
// 删除源对象属性时调用
}
});
传递给Proxy构造函数的第二个参数可称为处理方法(handler),这是一个包含了陷阱(套路)函数的对象,可以使我们能够拦截发生在源对象上的操作。
get()和set()就是两个陷阱,分别在调用dep.depend()和dep.notify()时触发。对于新添加的属性,也会调用set(),这样新添加的属性同样存在响应性。因此,我们不再需要使用Vue.$set()来添加新的响应性属性。同理,deleteProperty()同样适用。
使用Proxy实现响应式
尽管Proxy还没有被集成到Vue的响应引擎中,但是我们可以尝试一下,使用Proxy来实现之前文章中的例子。首先要更改的是Object.keys(data).forEach,我们现在将使用它为每个响应性属性创建一个新的依赖实例:
let deps = new Map(); // 创建一个Map对象
Object.keys(data).forEach(key => {
// 为每个属性都设置一个依赖实例 并放入 deps 中
deps.set(key, new Dep());
});
class Dep {
constructor () {
this.subscribers = []
}
depend () {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
let data_without_proxy = data; // 保存源对象
data = new Proxy(data_without_proxy, {
// 重写数据以在中间创建一个代理
get(obj, key) {
deps.get(key).depend(); // <-- 依旧为存储target
return obj[key]; // 返回原始数据
},
set(obj, key, newVal) {
obj[key] = newVal; // 将原始数据设置为新值
deps.get(key).notify(); // <-- 依旧为重新运行已存储的targets
return true;
}
});
注意:Dep class并不需要改动。单纯使用Proxy替换Object.defineProperty。
如你所见,我们创建了一个变量data_without_proxy来作为源对象的副本,在覆盖源对象时来使用副本创建一个Proxy对象。第二个参数是包含了get()和set()这两个陷阱函数属性的handler对象。
get(obj, key) => 是在访问属性时调用的函数。第一个参数obj为原始对象(data_without_proxy),第二个参数是被访问属性的key。这里面调用了与特定属性关联的特定方法(Dep class中的depend())。最后,使用return obj[key]返回与该key相关的值。
set(obj, key, newVal) => 中前两个参数与get的相同,第三个参数是新的修改值,然后,我们将新值设置给obj[key] = newVal修改的属性上,并调用notify()方法。
调整Total并测试
我们需要对代码再做一个小小的修改,将total提取到它自己的变量中,因为它不需要存在响应性:
let total = 0;
watcher(() => {
total = data.price * data.quantity;
});
console.log("total = " + total);
data.price = 20;
console.log("total = " + total);
data.quantity = 10;
console.log("total = " + total);
现在,重新运行,我们会在控制台中看到如下:
total = 10
total = 40
total = 200
这是个不错的进展,当我们更新price和quantity时,total更新。
添加新的响应性属性
现在,我们应该可以在不事先声明属性的情况下,将属性添加到data中。这可能就是使用Proxy,而不是Object.defineProperty()的原因之一。我们可以添加如下代码,来尝试一下:
deps.set("discount", new Dep()); // 为dep添加一个新属性
data["discount"] = 5; // 为data添加同样的新属性
let salePrice = 0;
watcher(() => { // 对其进行监听,其中包括我们新添加的属性
salePrice = data.price - data.discount;
});
console.log("salePrice = " + salePrice);
data.discount = 7.5; // 此时就会调用我们的监听函数,达到响应式的目的
console.log("salePrice = " + salePrice);
运行后,我们可以看到如下输出:
salePrice = 15
salePrice = 12.5
可以看到,当data.discount被修改时,salePrice也会更新。下面为完整的代码:
let data = { price: 5, quantity: 2 };
let target = null;
class Dep {
constructor() {
this.subscribers = [];
}
depend() {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target);
}
}
notify() {
this.subscribers.forEach(sub => sub());
}
}
// 前边的代码都没变
let deps = new Map(); // 创建一个Map对象
Object.keys(data).forEach(key => {
// 为每个属性都设置一个依赖实例 并放入 deps 中
deps.set(key, new Dep());
});
let data_without_proxy = data; // 保存源对象
data = new Proxy(data_without_proxy, {
// 重写数据以在中间创建一个代理
get(obj, key) {
deps.get(key).depend(); // <-- 依旧为存储target
return obj[key]; // 返回原始数据
},
set(obj, key, newVal) {
obj[key] = newVal; // 将原始数据设置为新值
deps.get(key).notify(); // <-- 依旧为重新运行已存储的targets
return true;
}
});
// 用来监听具有响应性属性的代码
function watcher(myFunc) {
target = myFunc;
target();
target = null;
}
let total = 0
watcher(() => {
total = data.price * data.quantity;
});
console.log("total = " + total);
data.price = 20;
console.log("total = " + total);
data.quantity = 10;
console.log("total = " + total);
deps.set('discount', new Dep())
data['discount'] = 5;
let salePrice = 0;
watcher(() => {
salePrice = data.price - data.discount;
});
console.log("salePrice = " + salePrice);
data.discount = 7.5
console.log("salePrice = " + salePrice);
总结
我们大致知道了Vue在未来版本如何使用Proxy来实现响应式,也了解到:
当前响应式引擎的局限性
Proxy是如何工作的
如何使用Proxy来搭建一个响应式引擎
proxy写监听方法,实现响应式的更多相关文章
- js监听rem实现响应式
原文链接:http://caibaojian.com/web-app-rem.html (function (doc, win) { var docEl = doc.documentElement, ...
- 简述Java中Http/Https请求监听方法
一.工欲善其事必先利其器 做Web开发的人总免不了与Http/Https请求打交道,很多时候我们都希望能够直观的的看到我们发送的请求参数和服务器返回的响应信息,这个时候就需要借助于某些工具啦.本文将采 ...
- jquery 事件监听方法
一.事件监听方法 mouseover() 鼠标移入事件方法 mouseout() 鼠标移出事件方法 mouseenter() 鼠标移入事件方法 mouseleave() 鼠标移出事件方法 ...
- Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法
时隔一个多月我又回来啦! 坚持真的是很难的一件事,其它事情稍忙,就很容易说服自己把写博客的计划给推迟了. 好在终于克服了自己的惰性,今天又开始了. 本篇继续我的Luaframework学习之路. 一. ...
- ORACLE 监听日志文件太大停止写监听日志引起数据库连接不上问题
生产库监听日志文件太大(达到4G多),发现oracle停止写监听日志,检查参数log_file,log_directory,log_status 均正常,数据库运行也正常. 经确认确实为监听日志过大引 ...
- vue父组件数据改变,子组件数据并未发生改变(那是因为你没写监听)附带子组件的写法
下面的代码有 父组件有三个按钮,年.月.日 点击之后父组件的数据发生改变,子组件却没改变,打印接受的数据,除了第一次其他都没打印,那是因为你没有写监听 <template> <div ...
- 手写实现vue的MVVM响应式原理
文中应用到的数据名词: MVVM ------------------ 视图-----模型----视图模型 三者与 Vue 的对应:view 对应 te ...
- Android View中的控件和监听方法...
PS:居然三天没写博客了...今天补上...东西虽多,但是都是一些基础...代码多了一些,有人可能会这样问,粘这么多代码有毛用..其实对于一个Android的初学者来说,一个完整的代码是最容易帮助理解 ...
- 后台自动运行,定期记录定位数据(Hbuilder监听 app由前台切换到后台、切换运行环境的 监听方法)
http://ask.dcloud.net.cn/question/28090 https://blog.csdn.net/qq_37508970/article/details/86649703 各 ...
随机推荐
- page next page prev
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <hea ...
- Servlet 学习总结-1
JavaWeb应用程序中所有的请求-响应都是由Servlet来完成的.Servlet是Java Web的核心程序,所有的网址(请求-响应)都交给Servlet来处理. Servlet在Web应用中被映 ...
- hdu2653之BFS
Waiting ten thousand years for Love Time Limit: 10000/2000 MS (Java/Others) Memory Limit: 32768/3 ...
- [LeetCode 题解]: Two Sum
前言 [LeetCode 题解]系列传送门: http://www.cnblogs.com/double-win/category/573499.html 1.题目描述 Given an a ...
- Linux常用命令,学的时候自己记的常用的保存下来方便以后使用 o(∩_∩)o 哈哈
service httpd restart 重启Apache service mysqld restart 重启mysql [-][rwx][r-x][r--] 1 234 567 890 421 4 ...
- BlangenOA项目展示(附源码)
1. 登录界面 1.1表单校验 1.2信息有误 1.3正在登录 2.桌面 3.用户管理 3.1添加 3.2删除 3.3编辑 3.4设置用户角色 3.5设置用户权限 4.角色管理 5.权限管理(菜单 ...
- Linux的用户及权限相关
sudo:用户想要使用sudo提升权限运行命令的话,需要把他加到sudo的list中 否则会报错:xxx is not in the sudoers file. 步骤 切换到root用户,运行visu ...
- MFC学习(一)
参考: VS项目属性的一些配置项的总结(important) 1. 项目配置 项目属性定制 常规(General) -> 平台工具集(Platform Toolset):vs2012中默认为&q ...
- RabbitMq初探——Hello World
HelloWorld 前言 这里我们弱化broker内部构造.将整体分为三部分. P:producer.生产者. C:Consumer.消费者. queue:队列. 后面的代码都依赖于 the php ...
- 现代 JavaScript 框架存在的主要原因
简评:现代 JavaScript 框架的出现最主要是解决哪个问题?这篇文章很好的解释了这个问题. 我见过许多人盲目地使用像 React,Angular 或 Vue.js 这样的现代框架.这些框架提供了 ...