使用 Proxy + Promise 实现 依赖收集
(深入浅出Vue基于“依赖收集”的响应式原理) ,这篇文章讲的是通过一个通俗易懂例子,介绍了 如何用Object.defineProperty 实现的“依赖收集”的原理。Object.defineProperty
属于ES5的特性,而ES6 带来了Proxy特性。这里先介绍一下:
看到这个让我想起了几年前我看过的印象深刻的一句话“ 框架是语法的补充”(网上搜索了下,居然找不到出处了),看起来, Javascript 现在居然自带Aspect Oriented Programming(AOP) 框架了。
Proxy 实现动态代理类似于middleware(中间件),可以对 对象 的基本操作进行统一处理,很适合实现 “依赖收集“。
而Vue.js 2.0 的响应式主要依赖 Object.defineProperty,其具有较好地浏览器兼容性,但是其无法直接监听数组下标方式变更以及动态添加的属性;而 Vue.js 3 中则计划
使用 ES6 Proxy 来实现响应式监听,其能够简化源代码、易于学习,并且还能带来更好地性能表现。(https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-
这边顺便提一下,我认为“依赖收集”的终极方案应该是ES7 的 Object.observe,不过被发起人自己撤除了。
下面通过简单的代码我们用 Proxy 来实现“依赖收集”的核心原理,为了让大家更好理解,我举了跟Object.defineProperty 相同的例子:
这是一个王者荣耀里的英雄:
const hero = {
health: 3000,
IQ: 150
}
一、使数据对象变得“可观测”
我们把原对象变成了可观测的代理对象 heroxy
const heroxy = new Proxy(hero,{
set (target, key, value, receiver) {
console.log(`我的${key}属性从$(target[key]) 变为 $(value)`);
return Reflect.set(target, key, value, receiver);//同样也是ES6新特性,比Object.defineProperty 更强大,更适合元编程,大家顺便一起学一下
}
};
我们来执行下:
heroxy.health = 5000;
heroxy.IQ = 200;
//-> 我的health属性从3000 变为 5000
//-> 我的200属性从150变为 200
代码确实简洁
二、计算属性
const heroxy = new Proxy(hero,{
set (target, key, value, receiver) {
console.log(`我的${key}属性从$(target[key]) 变为 $(value)`);
return Reflect.set(target, key, value, receiver);
}
get (target, key, receiver) {
if(key == "type"){
const _val = target.health > 4000 ? '坦克' : '脆皮';
console.log(`我的类型是:${val}`);
return _val ;
}
else{
Reflect.get(target, key, receiver);
}
}
};
heroxy.health = 5000
heroxy.IQ = 200
heroxy.type
//-> 我的health属性从3000 变为 5000
//-> 我的200属性从150变为 200
//-> 坦克
const computerDict = {
"type": {
computer(target){
return target.health > 4000 ? '坦克' : '脆皮';
},
onDepUpdated(val){
console.log(`我的类型是:${val}`);
}
},
}
const proDict = {
"health":[] , //为什么要定义集合,下面再说
"IQ":[]
}
const heroxy = new Proxy(hero,{
set (target, key, value, receiver) {
const _pro = proDict[key];
if(_pro){
console.log(`我的${key}属性从$(target[key]) 变
为 $(value)`);
}
const _com = computerDict[key];
if(_com){
console.error('计算属性无法被赋值!')
}
return Reflect.set(target, key, value, receiver);
},
get (target, key, receiver) {
const _com = computerDict[key];
if(_com){
const _val = _com.computer(target);
_com.onDepUpdated(_val);
return _val ;
}
else
{
return Reflect.get(target, key, receiver);
}
}
});
三、依赖收集
/**
* 定义一个“依赖收集器”
*/
const Dep = {
target: null
}
get (target, key, receiver) {
const _com = computerDict[key];
if(_com){
Dep.target = _com.onDepUpdated ;
const _val = _com.computer(target);
Dep.target = null;//临时存放一下而已
/ / _com.onDepUpdated(_val);
return _val ;
}
else
{
return Reflect.get(target, key, receiver);
}
}
get (target, key, receiver) {
const _com = computerDict[key];
if(_com){
Dep.target = ()=> {_com.onDepUpdated(_com.computer(heroxy)); };//这里要用heroxy
const _val = _com.computer(target);
Dep.target = null;
// _com.onDepUpdated(_val);
return _val ;
}
const _pro = proDict[key];
if(_pro){
if (Dep.target && _pro.indexOf(Dep.target) === -1) {
_pro.push(Dep.target);//明白了 proDict[key]定义为数组就是为了存放触发的函数集合
}
}
return Reflect.get(target, key, receiver);
}
set (target, key, value, receiver) {
const _pro = proDict[key];
if(_pro){
console.log(`我的${key}属性从${target[key]} 变为 ${value}`);
Reflect.set(target, key, value, receiver);
_pro.forEach((dep) =>{
dep();
});
return true ;
}
const _com = computerDict[key];
if(_com){
console.error('计算属性无法被赋值!')
}
return Reflect.set(target, key, value, receiver);
},
console.log(`英雄初始类型:${hero.type}`) hero.health = 5000
hero.health = 100
->英雄初始类型:脆皮
->我的health属性从100 变为 5000
->我的类型是:坦克
->我的health属性从5000 变为 100
->我的类型是:脆皮
四、自动更新的问题.....
hero.health = 5000
hero.health = 100
五、Promise 一次更新
"type": {
computer(target){
return target.health > 4000 ? '坦克' : '脆皮';
},
onDepUpdated(val){
new Promise(a=>a()).then(a=> {console.log(`我的类型是:${val}`);});
}
},
console.log(`英雄初始类型:${heroxy.type}`)
heroxy.health = 5000
heroxy.health = 100
英雄初始类型:脆皮
-> 我的health属性从3000 变为 5000
-> 我的health属性从5000 变为 100
-> 我的类型是:坦克
-> 我的类型是:脆皮
const Dep = {
target: null,
UpdateIndex:0
}
onDepUpdated(val,updateIndex){
new Promise(a=>a()).then(a=> {
if(updateIndex == Dep.UpdateIndex){
console.log(`我的类型是:${val}`);
Dep.UpdateIndex = 0 ;//记住操作完要把全局变量更新回去
}
} );
}
get (target, key, receiver) {
const _com = computerDict[key];
if(_com){
Dep.target = (updateIndex)=> { _com.onDepUpdated(_com.computer(heroxy),Dep.UpdateIndex);
};//传入参数
const _val = _com.computer(heroxy);
Dep.target = null;
// _com.onDepUpdated(_val);
return _val ;
}
const _pro = proDict[key];
if(_pro){
if (Dep.target && _pro.indexOf(Dep.target) === -1) {
_pro.push(Dep.target)
}
}
return Reflect.get(target, key, receiver);
}
set (target, key, value, receiver) {
const _pro = proDict[key];
if(_pro){
console.log(`我的${key}属性从${target[key]} 变为 ${value}`);
Reflect.set(target, key, value, receiver);
_pro.forEach((dep) =>{
Dep.UpdateIndex ++ ;//新增标记
dep(Dep.UpdateIndex);
});
return true ;
}
const _com = computerDict[key];
if(_com){
console.error('计算属性无法被赋值!')
}
return Reflect.set(target, key, value, receiver);
}
英雄初始类型:脆皮
我的health属性从3000 变为 5000
我的health属性从5000 变为 100
我的类型是:脆皮
const hero = {
health: 3000,
IQ: 150
}
const computerDict = {
"type": {
computer(target){
return target.health > 4000 ? '坦克' : '脆皮';
},
onDepUpdated(val,updateIndex){
new Promise(a=>a()).then(a=> {
if(updateIndex == Dep.UpdateIndex){
Dep.UpdateIndex = 0 ;
console.log(`我的类型是:${val}`);
}
});
}
},
}
const proDict = {
"health":[],
"IQ":[]
}
const Dep = {
target: null,
UpdateIndex:0
}
const heroxy = new Proxy(hero,{
set (target, key, value, receiver) {
const _pro = proDict[key];
if(_pro){
console.log(`我的${key}属性从${target[key]} 变为 ${value}`);
Reflect.set(target, key, value, receiver);
_pro.forEach((dep) =>{
Dep.UpdateIndex ++ ;
dep(Dep.UpdateIndex);
});
return true ;
}
const _com = computerDict[key];
if(_com){
console.error('计算属性无法被赋值!')
}
return Reflect.set(target, key, value, receiver);
},
get (target, key, receiver) {
const _com = computerDict[key];
if(_com){
Dep.target = (updateIndex)=> {_com.onDepUpdated(_com.computer(heroxy),Dep.UpdateIndex); };
const _val = _com.computer(heroxy);
Dep.target = null;
// _com.onDepUpdated(_val);
return _val ;
}
const _pro = proDict[key];
if(_pro){
if (Dep.target && _pro.indexOf(Dep.target) === -1) {
_pro.push(Dep.target)
}
}
return Reflect.get(target, key, receiver);
}
});
参考链接:
(深入浅出Vue基于“依赖收集”的响应式原理) https://zhuanlan.zhihu.com/p/29318017
http://es6.ruanyifeng.com/#docs/proxy)
Aspect Oriented Programming(AOP) 框架https://baike.baidu.com/item/AOP/1332219?fr=aladdin。
https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
https://esdiscuss.org/topic/an-update-on-object-observe
EventLoop (http://www.ruanyifeng.com/blog/2014/10/event-loop.html)
使用 Proxy + Promise 实现 依赖收集的更多相关文章
- 读Vue源码 (依赖收集与派发更新)
vue的依赖收集是定义在defineReactive方法中,通过Object.defineProperty来设置getter,红字部分主要做依赖收集,先判断了Dep.target如果有的情况会执行红字 ...
- 三、vue依赖收集
Vue 会把普通对象变成响应式对象,响应式对象 getter 相关的逻辑就是做依赖收集,这一节我们来详细分析这个过程 Dep Dep 是整个 getter 依赖收集的核心,它的定义在 src/core ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Vue.js依赖收集
写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出.文章的原地址:https://github.com/an ...
- Vue依赖收集引发的问题
问题背景 在我们的项目中有一个可视化配置的模块,是通过go.js生成canvas来实现的.但是,我们发现这个模块在浏览器中经常会引起该tab页崩溃.开启chrome的任务管理器一看,进入该页面内存和c ...
- 深入浅出Vue基于“依赖收集”的响应式原理(转)
add by zhj: 文章写的很通俗易懂,明白了Object.defineProperty的用法 原文:https://zhuanlan.zhihu.com/p/29318017 每当问到VueJS ...
- Vue 依赖收集原理分析
此文已由作者吴维伟授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Vue实例在初始化时,可以接受以下几类数据: 模板 初始化数据 传递给组件的属性值 computed wat ...
- vue的双向绑定和依赖收集
在掘金上买了一个关于解读vue源码的小册,因为是付费的,所以还比较放心 在小册里看到了关于vue双向绑定和依赖收集的部分,总感觉有些怪怪的,然后就自己跟着敲了一遍. 敲完后,发现完全无法运行, 坑啊 ...
- webpack源码-依赖收集
webpack源码-依赖收集 version:3.12.0 程序主要流程: 触发make钩子 Compilation.js 执行EntryOptionPlugin 中注册的make钩子 执行compi ...
随机推荐
- 第二次作业——个人项目实战(Sudoku)
Github:Sudoku 项目相关要求 利用程序随机构造出N个已解答的数独棋盘 . 输入 数独棋盘题目个数N 输出 随机生成N个 不重复 的 已解答完毕的 数独棋盘,并输出到sudoku.txt中, ...
- 201521123056 《Java程序设计》第8周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 1.1 思维导图: 2. 书面作业 本次作业题集集合 1. List中 ...
- 201521123111《Java程序设计》第6周学习总结
1. 本章学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...
- 201521123042 Java第一周学习总结
1. 201521123042 <Java程序设计>第一周学习总结 a.用notepad++和eclipse编写Java程序 b.安装Java Q1.为什么java程序可以跨平台运行?执行 ...
- 201521123023《Java程序设计》第12周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. (1)File可以表示一个特定文件的名称,也能表示一个目录下的一组文件的名称. (2)继承自InputStre ...
- 201521123022 《Java程序设计》 第十三周学习总结
1. 本周学习总结 2. 书面作业 Q1. 网络基础 Q1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? 前者IP ...
- 【转】SWT/JFace的对话框
一.MessageDialog ,MessageDialog的用法很简单 MessageDialog.openInfomation(shell,title,message); ...
- 监控-CPU使用率
原始脚本来自TG,自己对部分脚本做了调整,分享出来仅供参考,请勿整篇Copy! 使用以下语句获取[CPU使用率] USE [DBA_Monitor] GO /****** 对象: StoredProc ...
- Socket类 以及 ServerSocket类 讲解
Socket类 套接字是网络连接的端点,套接字使应用可以从网络中读取数据,可以向网络中写入数据.不同计算机上的两个应用程序可以通过连接发送或接收字节流,以此达到相互通信的目的. 为了从一个应用程序向另 ...
- Ansible系列(七):执行过程分析、异步模式和速度优化
本文目录:1.1 ansible执行过程分析1.2 ansible并发和异步1.3 ansible的-t选项妙用1.4 优化ansible速度 1.4.1 设置ansible开启ssh长连接 1.4. ...