Vue2剥丝抽茧-响应式系统 系列

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北 籍 前端群,可加我微信进群。


如果打算学vue2源码,可以看看这个系列https://vue.windliang.wang

目前工作中大概有 40% 的需求是在用 Vue2 的技术栈,所谓知其然更要知其所以然,为了更好的使用 Vue 、更快的排查问题,最近学习了源码相关的一些知识,虽然网上总结 Vue 的很多很多了,不少自己一个,但也不多自己一个,欢迎一起讨论学习,发现问题欢迎指出。

响应式系统要干什么

回到最简单的代码:

data = {text: 'hello, world'
}const updateComponent = () => {console.log('收到', data.text);
}updateComponent()data.text = 'hello, liang'
// 运行结果
// 收到 hello, world

响应式系统要做的事情:某个依赖了 data 数据的函数,当所依赖的 data 数据改变的时候,该函数要重新执行。

我们期望的效果:当上边 data.text 修改的时候, updateComponent 函数再执行一次。

为了实现响应式系统,我们需要做两件事情:

  1. 知道 data 中的数据被哪些函数依赖

  2. data 中的数据改变的时候去调用依赖它的函数们

为了实现第 1 点,我们需要在执行函数的时候,将当前函数保存起来,然后在读取数据的时候将该函数保存到当前数据中。

2 点就迎刃而解了,当修改数据的时候将保存起来的函数执行一次即可。

读取数据修改数据的时候需要做额外的事情,我们可以通过 Object.defineProperty()  重写对象属性的 getset 函数。

响应式数据

我们来写一个函数,重写属性的 getset 函数。

/*** Define a reactive property on an Object.*/
export function defineReactive(obj, key, val) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;/*********************************************/// 1.这里需要去保存当前在执行的函数/*********************************************/return value;},set: function reactiveSetter(newVal) {const value = getter ? getter.call(obj) : val;if (setter) {setter.call(obj, newVal);} else {val = newVal;}/*********************************************/// 2.将依赖当前数据依赖的函数执行/*********************************************/},});
}

为了调用更方便,我们把第 1 步和第 2 步的操作封装一个 Dep  类。

export default class Dep {static target; //当前在执行的函数subs; // 依赖的函数constructor() {this.subs = []; // 保存所有需要执行的函数}addSub(sub) {this.subs.push(sub);}depend() {// 触发 get 的时候走到这里if (Dep.target) {// 委托给 Dep.target 去调用 addSubDep.target.addDep(this);}}notify() {for (let i = 0, l = this.subs.length; i < l; i++) {this.subs[i].update();}}
}Dep.target = null; // 静态变量,全局唯一

我们将当前执行的函数保存到 Dep 类的 target 变量上。

保存当前正在执行的函数

为了保存当前的函数,我们还需要写一个 Watcher 类,将需要执行的函数传入,保存到 Watcher 类中的 getter 属性中,然后交由 Watcher 类负责执行。

这样在 Dep 类中, subs 中保存的就不是当前函数了,而是持有当前函数的 Watcher 对象。

import Dep from "./dep";
export default class Watcher {constructor(Fn) {this.getter = Fn;this.get();}/*** Evaluate the getter, and re-collect dependencies.*/get() {Dep.target = this; // 保存包装了当前正在执行的函数的 Watcherlet value;try {// 调用当前传进来的函数,触发对象属性的 getvalue = this.getter.call();} catch (e) {throw e;}return value;}/*** Add a dependency to this directive.*/addDep(dep) {// 触发 get 后会走到这里,收集当前依赖// 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中dep.addSub(this);}/*** Subscriber interface.* Will be called when a dependency changes.*/// 修改对象属性值的时候触发 set,走到这里update() {this.run();}/*** Scheduler job interface.* Will be called by the scheduler.*/run() {this.get();}
}

Watcher 的作用就是将正在执行的函数通过 Watcher 包装后保存到 Dep.target 中,然后调用传进来的函数,此时触发对象属性的 get 函数,会收集当前 Watcher

如果未来修改对象属性的值,会触发对象属性的 set ,接着就会调用之前收集到的 Watcher 对象,通过 Watcher 对象的 uptate 方法,来调用最初执行的函数。

响应式数据

回到我们之前没写完的 defineReactive 函数,按照上边的思路,我们来补全一下。

import Dep from "./dep";
/*** Define a reactive property on an Object.*/
export function defineReactive(obj, key, val) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}/*********************************************/const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher/*********************************************/Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;/*********************************************/// 1.这里需要去保存当前在执行的函数if (Dep.target) {dep.depend();}/*********************************************/return value;},set: function reactiveSetter(newVal) {const value = getter ? getter.call(obj) : val;if (setter) {setter.call(obj, newVal);} else {val = newVal;}/*********************************************/// 2.将依赖当前数据依赖的函数执行dep.notify();/*********************************************/},});
}

Observer 对象

我们再写一个 Observer 方法,把对象的全部属性都变成响应式的。

export class Observer {constructor(value) {this.walk(value);}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}
}

我们提供一个 observe 方法来负责创建 Observer 对象。

export function observe(value) {let ob = new Observer(value);return ob;
}

测试

将上边的方法引入到文章最开头的例子,来执行一下:

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {text: "hello, world",
};
// 将数据变成响应式的
observe(data);const updateComponent = () => {console.log("收到", data.text);
};// 当前函数由 Watcher 进行执行
new Watcher(updateComponent);data.text = "hello, liang";

此时就会输出两次了~

收到 hello, world
收到 hello, liang

说明我们的响应式系统成功了。

1c96b3f9599f44865467db55569dea4a.png
image-20220329092722630

先从整体理解了响应式系统的整个流程:

每个属性有一个 subs 数组,Watcher 会持有当前执行的函数,当读取属性的时候触发 get ,将当前 Watcher 保存到 subs 数组中,当属性值修改的时候,再通过 subs 数组中的 Watcher 对象执行之前保存的函数。

当然还有亿点点细节需要完善,后边的文章会继续。vue2源码系列文章,作者现在写了12篇了。 https://vue.windliang.wang

cd36d00c3f39fe0e5c02d3594a7e4cbe.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

b21578f3a4a93fe444e03915833c52d3.png

扫码加我微信 ruochuan02、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 ruochuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

word文本样式代码样式_使用文本样式表达创建真相来源

word文本样式代码样式As of After Effects 17.0, you can use expressions to edit text styles in After Effects. Here’s why this would transform your workflow:从After Effects 17.0开始&#xff0c;您可以使用表达式在After Effects中编辑文本样式。 这就是这将改变您的…

mvn备忘

创建web工程 mvn archetype:generate -DgroupIdcom.malangmedia -DartifactIdautoDeployToJetty -DarchetypeArtifactIdmaven-archetype-webapp -Dversion1.0 添加jetty插件 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.or…

前端框架源码解读之Vite

前端工具链十年盘点&#xff1a;https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfgWebpack、Rollup 、Esbuild、Vite ?webpack: 基于 JavaScript 开发的前端打包构建框架&#xff0c;通过依赖收集&#xff0c;模块解析&#xff0c;生成 chunk&#xff0c;最终输出生成的打包…

hp-ux_UX中的格式塔-或-为什么设计师如此讨厌间距

hp-uxI’ve been lucky so far in my design career to have worked with engineers that seem genuinely interested in learning about design. Perhaps, as mentioned in the title, it’s more about them trying to figure out why it matters so much to us that there i…

很多人都不知道,其实博客园给我们博客开了二级域名

如题。一直都在邮件签名里写自己的博客地址为&#xff1a; http://www.cnblogs.com/datacool&#xff1b;直到有天突然发现使用&#xff1a;http://datacool.cnblogs.com也可以访问。不知道的赶紧测试&#xff0c;后者明显要酷很多啊。该不是我是最后一个知道的吧&#xff0c;知…

JavaScript 数组新增 4 个非破坏性方法!

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

自行车改装电动车怎么样_电动车听起来应该是什么样?

自行车改装电动车怎么样The sound of an all-electric car accelerating doesn’t have to sound like a standard combustion engine, It could sound like anything.全电动汽车加速的声音不必听起来像是标准的内燃机&#xff0c;它可以听起来像任何东西。 These were the wor…

C++中的三种继承public,protected,private(转)

三种访问权限 public:可以被任意实体访问 protected:只允许子类及本类的成员函数访问 private:只允许本类的成员函数访问 三种继承方式 public 继承 protect 继承 private 继承 组合结果 基类中 继承方式 子类中 public &#xff06; public继承 > public public &#xff0…

如何碎片化时间学前端,了解前沿趋势

我很开心在前端行业认识了一批优秀且乐于分享的朋友&#xff0c;他们的技术分享与职业观点让我获益良多&#xff0c;推荐给大家一起关注。程序员成长指北Node.js 前端工程化 低代码考拉小姐姐&#xff0c;一个有趣且乐于分享的人&#xff01;目前就职于某知名外企&#xff0c;负…

谷歌pay破解_Google Pay缺少Google闻名的一件事-UX案例研究

谷歌pay破解Disclaimer: The views expressed in the blog post is purely based on personal experience. It was not influenced by any external factor.When Google launched Tez (now Google Pay) in India during 2017, their primary goal was to design a simple payme…

进阶高级前端,这位大前端架构师一定不能错过

今天给大家介绍一位好朋友&#xff1a;这波能反杀&#xff1a;一位拥有十年工作经验&#xff0c;对学习方法有独到理解的资深大前端架构师。一、博客早在 2017 年初&#xff0c;波神在简书平台以《前端基础进阶》为名&#xff0c;更新了一系列优质文章&#xff0c;获得大量认可…

memcached应用策略(转)

memcached应用策略&#xff08;转&#xff09;(2012-04-05 11:10:02) 转载▼标签&#xff1a; memcached 应用策略 it分类&#xff1a; linux_c memcached应用策略memcached 主要的作用是为减轻大访问量对数据库的冲击&#xff0c;所以一般的逻辑是首先从memcached中读取数据&a…

突然讨厌做前端,讨厌代码_为什么用户讨厌重新设计

突然讨厌做前端,讨厌代码重点 (Top highlight)The core of design thinking is to only design something that will bring value and fill the gap in consumer needs. Right? Why else would one design something that no one asked for? While that may be true to some …

那些年我面过的「六年经验」的初级工程师

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

sql反模式分析2

第八章 多列属性目标&#xff1a;存储多值属性 为一个bug设置多个标签反模式&#xff1a;创建多个列&#xff0c;为bugs创建tag1&#xff0c;tag2&#xff0c;tag3几个列保存标签。标签必须放于其中一个。1.查询数据&#xff0c;比如搜索这三列&#xff0c;可以使用in语句2.添…

更多信息请关注微信公众号_为什么我们更多地关注表面异常?

更多信息请关注微信公众号Don’t you feel lucky to find a single seasoned curly fry in your bunch of plain old boring french fries? Do you remember highlighting important texts of your study materials before the exams? Both situations might seem irrelevant…

eclipse中的汉字极小的解决方案(转载)

eclipse中的汉字极小的解决方案(转载) 可能新装了eclipse后&#xff0c;写java代码的时候发现&#xff0c;写注释的时候发现&#xff0c;汉字小的可怜&#xff0c;网上搜一下&#xff0c;又是改字体又是设置字体大小&#xff0c;试用后发现都不是针对这个的方法。 无奈在自己摸…

面试官经常问的观察者模式如何实现~

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

旅行者 问题_门槛项目:没有旅行者回到他的原籍城市。

旅行者 问题Sohini Mukherjee| MFA| Spring 2020Sohini Mukherjee | 外交部| 2020年Spring Artivive app to see the full Artivive应用程序可查看完整的#AR experience.#AR体验。 Prompt:提示&#xff1a; As second semester, first year graduate students, you are at a …

产品经理懂技术=流氓会武术(zz)

最近七年&#xff0c;我都在做互联网产品&#xff0c;其中前五年分别在创业公司和上市公司里&#xff0c;做别人的产品&#xff1b;近两年在创业&#xff0c;做自己的产品。 我的体会是&#xff1a;产品经理需要懂技术&#xff0c;创业者尤其需要。但前提是你总觉得有股憋不住的…