JavaScript 如何实现一个响应式系统
JavaScript 如何实现一个响应式系统
第一阶段目标
- 数据变化重新运行依赖数据的过程
第一阶段问题
- 如何知道数据发生了变化
- 如何知道哪些过程依赖了哪些数据
第一阶段问题的解决方案
- 我们可用参考现有的响应式系统(vue)
- 本次示例使用
Proxy实现数据监控,Proxy详细信息查看官网。 - 根据解决方案,需要改变第一阶段目标为->
Proxy对象变化重新运行依赖数据的过程 - 问题变更->如何知道
Proxy发生了变化 - 问题变更->如何知道哪些函数依赖了哪些
Proxy
如何知道 Proxy 对象发生了变化,示例代码
//这里传入一个对象,返回一个Proxy对象,对Proxy对象的属性的读取和修改会触发内部的get,set方法
function relyOnCore(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
return new Proxy(obj, {
get(target, key, receiver) {
return target[key];
},
set(target, key, value, receiver) {
//这里需要返回是否修改成功的Boolean值
return Reflect.set(target, key, value);
},
});
}
数据监控初步完成,但是这里只监控了属性的读取和设置,还有很多操作没有监控,以及数据的 this 指向,我们需要完善它
//完善后的代码
export function relyOnCore(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
return new Proxy(obj, {
get(target, key, receiver) {
if (typeof target[key] === "object" && target[key] !== null) {
//当读取的值是一个对象,需要重新代理这个对象
return relyOnCore(target[key]);
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
ownKeys(target) {
return Reflect.ownKeys(target);
},
getOwnPropertyDescriptor(target, key) {
return Reflect.getOwnPropertyDescriptor(target, key);
},
has(target, p) {
return Reflect.has(target, p);
},
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key);
},
defineProperty(target, key, attributes) {
return Reflect.defineProperty(target, key, attributes);
},
});
}
如何知道哪些函数依赖了哪些 Proxy 对象
问题:依赖 Proxy 对象的函数要如何收集
在收集依赖 Proxy 对象的函数的时候出现了一个问题: 无法知道数据在什么环境使用的,拿不到对应的函数
解决方案
既然是因为无法知道函数的执行环境导致的无法找到对应函数,那么我们只需要给函数一个固定的运行环境就可以知道函数依赖了哪些数据。
示例
//定义一个变量
export let currentFn;
export function trackFn(fn) {
return function FnTrackEnv() {
currentFn = FnTrackEnv;
fn();
currentFn = null;
};
}
自此,我们的函数调用期间 Proxy 对象监听到的数据读取在 currentFn 函数内部发生的。
同样,我们的目标从最开始的 数据变化重新运行依赖数据的过程 -> Proxy 对象变化重新运行依赖收集完成的函数
完善函数调用环境
直接给全局变量赋值,在函数嵌套调用的情况下,这个依赖收集会出现问题
let obj1 = relyOnCore({ a: 1, b: 2, c: { d: 3 } });
function fn1() {
let a = obj1.a;
function fn2() {
let b = obj1.b;
}
//这里的c会无法收集依赖
let c = obj1.c;
}
我们修改一下函数收集
export const FnStack = [];
export function trackFn(fn) {
return function FnTrackEnv() {
FnStack.push(FnTrackEnv);
fn();
FnStack.pop(FnTrackEnv);
};
}
第二阶段目标
- 在合适的时机触发合适的函数
第二阶段问题
- 在什么时间触发函数
- 到达触发时间时,应该触发什么函数
第一个问题:在什么时间触发函数
必然是在修改数据完成之后触发函数
第二个问题:应该触发什么函数
当操作会改变函数读取的信息的时候,需要重新运行函数。因此,我们需要建立一个映射关系
{
//对象
"obj": {
//属性
"key": {
//对属性的操作
"handle": ["fn"] //对应的函数
}
}
}
在数据改变的时候,我们只需要根据映射关系,循环运行 handle 内的函数
数据读取和函数建立联系
我们可以创建一个函数用于建立这种联系
export function track(object, handle, key, fn) {}
这个函数接收 4 个参数,object(对象),handle(对数据的操作类型) key(操作了对象的什么属性),fn(需要关联的函数)
我们现在来创建映射关系
export const ObjMap = new WeakMap();
export const handleType = {
GET: "GET",
SET: "SET",
Delete: "Delete",
Define: "Define",
Has: "Has",
getOwnPropertyDescriptor: "getOwnPropertyDescriptor",
ownKeys: "ownKeys",
};
export function track(object, handle, key, fn) {
setObjMap(object, key, handle, fn);
}
function setObjMap(obj, key, handle, fn) {
if (!ObjMap.has(obj)) {
ObjMap.set(obj, new Map());
}
setKeyMap(obj, key, handle, fn);
}
const setKeyMap = (obj, key, handle, fn) => {
let keyMap = ObjMap.get(obj);
if (!keyMap.has(key)) {
keyMap.set(key, new Map());
}
setHandle(obj, key, handle, fn);
};
const setHandle = (obj, key, handle, fn) => {
let keyMap = ObjMap.get(obj);
let handleMap = keyMap.get(key);
if (!handleMap.has(handle)) {
handleMap.set(handle, new Set());
}
setFn(obj, key, handle, fn);
};
const setFn = (obj, key, handle, fn) => {
let keyMap = ObjMap.get(obj);
let handleMap = keyMap.get(key);
let fnSet = handleMap.get(handle);
fnSet.add(fn);
};
现在已经实现了数据和函数之间的关联只需要在读取数据时调用这个方法去收集依赖就可以,代码如下:
export function relyOnCore(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
return new Proxy(obj, {
get(target, key, receiver) {
track(target, handleType.GET, key, FnStack[FnStack.length - 1]);
if (typeof target[key] === "object" && target[key] !== null) {
return relyOnCore(target[key]);
}
return Reflect.get(target, key, receiver);
},
//....这里省略剩余代码
});
}
接下来我们需要建立数据改变->影响哪些数据的读取之间的关联
export const TriggerToTrackMap = new Map([
[handleType.SET, [handleType.GET, handleType.getOwnPropertyDescriptor]],
[
handleType.Delete,
[
handleType.GET,
handleType.ownKeys,
handleType.Has,
handleType.getOwnPropertyDescriptor,
],
],
[handleType.Define, [handleType.ownKeys, handleType.Has]],
]);
建立这样关联后,我们只需要在数据变动的时候,根据映射关系去寻找需要重新运行的函数就可以实现响应式。
export function trigger(object, handle, key) {
let keyMap = ObjMap.get(object);
if (!keyMap) {
return;
}
let handleMap = keyMap.get(key);
if (!handleMap) {
return;
}
let TriggerToTrack = TriggerToTrackMap.get(handle);
let fnSet = new Set();
TriggerToTrack.forEach((handle) => {
let fnSetChiren = handleMap.get(handle);
if (fnSetChiren) {
fnSetChiren.forEach((fn) => {
if (fn) {
fnSet.add(fn);
}
});
}
});
fnSet.forEach((fn) => {
fn();
});
}
总结
以上简易的实现了响应式系统,只是粗略的介绍了如何实现,会存在一些 bug
JavaScript 如何实现一个响应式系统的更多相关文章
- 使用Javascript来创建一个响应式的超酷360度全景图片查看幻灯效果
360度的全景图片效果常常可以用到给客户做产品展示,今天这里我们推荐一个非常不错的来自Robert Pataki的360全景幻灯实现教程,这里教程中将使用javascript来打造一个超酷的全景幻灯实 ...
- vue原理探索--响应式系统
Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,但是对这些对象进行操作时,却能影响对应视图,它的核心实现就是「响应式系统」. 首先看一下 Object.defi ...
- Vue 及框架响应式系统原理
个人bolg地址 全局概览 Vue运行内部运行机制 总览图: 初始化及挂载 在 new Vue()之后. Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会初始化生命周期. ...
- Vuejs - 深入浅出响应式系统
Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 Javascript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接,不过理解其工作原理同样非常重要,这样 ...
- Vue的响应式系统
Vue的响应式系统 我们第一次使用Vue的时候,会感觉有些神奇,举个例子: <div id="app"> <div>价格:¥{{price}}</di ...
- 【js】vue 2.5.1 源码学习 (七) 初始化之 initState 响应式系统基本思路
大体思路(六) 本节内容: 一.生命周期的钩子函数的实现 ==> callHook(vm , 'beforeCreate') beforeCreate 实例创建之后 事件数据还未创建 二.初始化 ...
- 前端必读:Vue响应式系统大PK
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/vue-3-reactivity-system ...
- 使用jQuery开发一个响应式超酷整合RSS信息阅读杂志
在线演示1 本地下载 申请达人,去除赞助商链接 如果大家喜欢阅读博客文章的话,可能都会使用RSS阅读器,今天这里我们将使用jQuery来开发一个响应式的RSS信息阅读应用,使用它你可以将你喜欢 ...
- AudioPlayer.js,一个响应式且支持触摸操作的jquery音频插件
AudioPlayer.js是一个响应式.支持触摸操作的HTML5 的音乐播放器.本文是对其官网的说用说明文档得翻译,博主第一次翻译外文.不到之处还请谅解.之处. JS文件地址:http://osva ...
- 你是如何理解Vue的响应式系统的
1.响应式系统简述: 任何一个 Vue Component 都有一个与之对应的 Watcher 实例. Vue 的 data 上的属性会被添加 getter 和 setter 属性. 当 Vue Co ...
随机推荐
- 使用Go语言开发一个短链接服务:一、基本原理
章节 使用Go语言开发一个短链接服务:一.基本原理 使用Go语言开发一个短链接服务:二.架构设计 使用Go语言开发一个短链接服务:三.项目目录结构设计 使用Go语言开发一个短链接服务:四.生成 ...
- KingbaseES V8R6集群运维案例-- 备库数据库服务意外down分析
案例说明: KingbaseES V8R6一主二备架构的集群,两个备库节点sys_log日志分别不同时间点收到'fast shutdown request'的日志信息,导致备库数据库服务down,需要 ...
- Java面试题【3】
20)什么是线程安全? 含义:当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以 ...
- PyCharm专业版延长使用时间【极简】
关注公众号[靠谱杨阅读人生]回复[py]获取破解包! 准备好最新版本的PyCharm(去官网下载页面上的第一个就可以,我使用的版本如下图所示) 打开软件选择试用,进去之后可以新建一个项目然后把这个压缩 ...
- 双向循环链表(DoubleLoopLinkList)
双向循环链表 关于双向循环链表可以先阅读这篇文章这里就不再赘述:双向链表(DoubleLinkList) Node template<typename T> class Node { pu ...
- 使用Python插入100万条数据到MySQL数据库并将数据逐步写出到多个Excel
Python插入100万条数据到MySQL数据库 步骤一:导入所需模块和库 首先,我们需要导入 MySQL 连接器模块和 Faker 模块.MySQL 连接器模块用于连接到 MySQL 数据库,而 F ...
- Numpy广播功能
广播(Broadcast)是对不同形状(shape)的数组进行数值计算的方式. 广播规则: 如果两个数组的维度数不相同,那么小维度数组的形状将会在最左边补1: 如果两个数组的形状在任何一个维度都不匹配 ...
- 20个Python 正则表达式应用与技巧
本文分享自华为云社区<Python 正则表达式大揭秘应用与技巧全解析>,作者:柠檬味拥抱. Python 中的 re 模块是用于处理正则表达式的强大工具.正则表达式是一种用来匹配字符串的模 ...
- HarmonyOS:Neural Network Runtime对接AI推理框架开发指导
场景介绍 Neural Network Runtime作为AI推理引擎和加速芯片的桥梁,为AI推理引擎提供精简的Native接口,满足推理引擎通过加速芯片执行端到端推理的需求. 本文以图1展示的A ...
- 鸿蒙智联生态产品《接入智慧生活App开发指导》(官方更新版)
原文:https://mp.weixin.qq.com/s/BDC-12aiZz2EhtjYLR7QIg,点击链接查看更多技术内容. 在HarmonyOS Connect生态产品应用开发过程中,很多开 ...