前言

很多时候我们都对源码展现出了一定的渴求,但当被问到究竟为什么想看源码时,答案无非也就那么几种:

  • 为了面试
  • 为了在简历上写自己会源码
  • 了解底层原理 学习高手思路
  • 通过源码来学习一些小技巧(骚操作)
  • 对框架如何实现的各种功能感到好奇
  • 内卷严重 不看不行 逆水行舟 不进则退
  • 自己也想造轮子 先看看别人都是怎么做的
  • 各种公众号和卖课的都在贩卖焦虑 被洗脑洗的

但其实很少人会真正的看明白源码,一方面是由于代码量实在是太多了,另一方面则是当我们阅读别人代码的时候就是容易搞得一头雾水。因为每个人的编码方式以及编码习惯都大相径庭,在看一个编码习惯与自己不同的人的代码时是很累的。

况且不仅是由于每个人的编码风格相差甚远,人与人之间各自擅长的技术方向以及技术水平也都是横看成岭侧成峰远近高低各不同。刨除掉以上的种种原因之后,更重要的一个原因是很多人框架用的都不够精通呢、用过的API也就那么几个常见的,其他不常用但很高阶的API都没怎么用过,连用都没用明白呢,这样的人看源码的时候当然会被绕晕啦!

那肯定有人会说:尤雨溪他框架就一定用的很6吗?我每天都在用他的框架写代码,他还不一定有我熟练呢!

这么说确实有一定的道理,但如果论底层,他比谁都了解。之所以我们啃不动源码的很重要的一个原因就是:细枝末节的东西实在是太多了,很容易令大家找不到重点。这些细枝末节的东西自然有它们存在的道理,但它们确成为了我们行走在钻研源码这条路上的绊脚石。

题外话

怎样学习源码才是最科学的方式呢?我们来看一个例子:有一些听起来非常高大上的高科技产品,如电磁轨道炮。各个军事强国都在争相探索这一领域,假设有一天,我们一觉醒来成为了国家电磁轨道炮首席研究员,是专门负责研究电磁轨道炮底层技术的。那么当我们拆解一个电磁轨道炮的时候,大概率你是看不懂它的内部构造的。因为里面会包含许多非常复杂的高强度材料控制磁力的电极蜿蜒曲折的电线提高精准度的装置以及一些利于使用者操控的封装等等…

那么此时的你可能就不太容易搞明白电磁轨道炮的真正原理,直到有一次在网上偶然间看到一个视频,视频中的人用了一些磁铁、若干钢珠、以及几个我们日常生活中能够搞到的材料来制作了一个简易版的电磁轨道炮。这样我们一下子就能够搞懂电磁轨道炮的真正原理,虽然这样的轨道炮并不能真正的用于实战,但只要我们明白了最基础的那部分,我们就可以在此基础上一步步进行扩展,慢慢弄懂整个能够用于实战的复杂轨道炮。

源码也是同理,我们按照电磁轨道炮的思路一步步来,先搞清楚最核心的基础部分,慢慢的再一步步去进阶。这样的学习方法比我们肯定一上来就去拆解一个完整版的电磁轨道炮要强得多

既然我们有这样的需求,那么作为一个流行框架的作者就必然会有所回应:在一次培训的过程中,尤雨溪带领大家写了一个非常微型的Vue3。不过可惜这是他在国外办过的为期一天的培训,我们国内的观众并没有福气能够享受到被框架作者培训的这么一次教学。但好在尤雨溪已经把代码全部上传到了codepen上,大家可以点击这个链接来阅读尤雨溪亲手写的代码,或者也可以选择留在本篇文章内,看我来用中文为大家讲解尤雨溪的亲笔代码

响应式篇

尤雨溪在某次直播时曾表示过:Vue3 的源码要比 Vue2 的源码要好学很多Vue3在架构以及模块的耦合关系设计方面比Vue2更好,可以一个模块一个模块看,这样比较容易理解。如果是刚上手,可以从Reactivity看起。因为Reactivity是整个Vue3中跟外部没有任何耦合的一个模块。

Reactivity就是我们常说的响应式,大名鼎鼎的React也是这个意思,不信仔细对比一下前五个字母。那么什么是响应式呢?想想看React是什么框架?MVVM对吧?MVVM的主打口号是:

数据驱动视图!

也就是说当数据发生改变时我们会重新渲染一下组件,这样就能够达到一修改数据,页面上用到这个数据的地方就会实时发生变化的效果。不过在数据发生变化时也不仅仅只是能够更新视图,还可以做些别的呢!尤雨溪在创建@vue/reactivity这个模块的时候,借鉴的是@nx-js/observer-util这个库。我们来看一眼它在GitHubREADME.md里展示的一段示例代码:

import { observable, observe } from '@nx-js/observer-util';const counter = observable({ num: 0 });
const countLogger = observe(() => console.log(counter.num));// 这行代码将会调用 countLogger 这个函数并打印出:1
counter.num++;

是不是很像Vue3reactivewatchEffect啊?其实就是我们提前定义好一个函数,当函数里面依赖的数据项发生变化时就会自动执行这段函数,这就是响应式!

数据驱动视图那就更容易理解了,既然当数据发生变化时可以执行一段函数,那么这段函数为什么不可以执行一段更新视图的操作呢:

import { store, view } from 'react-easy-state';const counter = store({num: 0,up() {this.num++;}
});// 这是一个响应式的组件, 当 counter.num 发生变化时会自动重新渲染组件
const UserComp = view(() => <div onClick={counter.up}>{counter.num}</div>);

react-easy-state是他们(尤雨溪借鉴的那个库)专门针对React来进行封装的,不难看出view这个函数就是observe函数的一个变形,observe是要你传一个函数进去,你函数里面想执行啥就执行啥。而view是要你传一个组件进去,当数据变化时会去执行他们提前写好的一段更新逻辑,那不就跟你自己在observe里写一段更新操作是一样的嘛!用了这个库写出来的React就像是在写Vue一样。

源码

理解了什么是响应式之后就可以方便我们来查看源码了,来看看尤雨溪是怎么仅用十几行代码就实现的响应式

let activeEffectclass Dep {subscribers = new Set()depend() {if (activeEffect) {this.subscribers.add(activeEffect)}}notify() {this.subscribers.forEach(effect => effect())}
}function watchEffect(effect) {activeEffect = effecteffect()
}

实现完了,再来看看该怎么用:

const dep = new Dep()let actualCount = 0
const state = {get count() {dep.depend()return actualCount},set count(newCount) {actualCount = newCountdep.notify()}
}watchEffect(() => {console.log(state.count)
}) // 0state.count++ // 1

如果在观看这十几二十来行代码时都会觉得绕的话,那就说明你的基础属实不怎么样。因为明眼人一眼就可以看出来,这是一个非常经典的设计模式:发布-订阅模式

发布-订阅模式

如果不太了解发布-订阅模式的话,我们可以简单的来讲一下。但如果你对这些设计模式早已了如指掌,并且能够轻松读懂刚才那段代码的话,建议暂且先跳过这一段。

《JavaScript设计模式与开发实践》一书中,作者曾探发布-订阅模式举了一个十分生动形象的例子:

小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼 MM 告诉小明,不久之后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。但到底是什么时候,目前还没有人能够知道。

于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除了小明,还有小红、小强、小龙也会每天向售楼处咨询这个问题。一个星期过后,售楼 MM 决定辞职,因为厌倦了每天回答 1000 个相同内容的电话。

当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号留在了售楼处。售楼 MM 答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一样,他们的电话号码都被记载售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们。

在刚刚的例子中,发送短信通知就是一个典型的发布-订阅模式,小明、小红等购买者都是订阅者,他们订阅了房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话号码,依次给购房者发布消息。

如果你曾经用过xxx.addEventListener这个函数为DOM添加过事件的话,那么实际上就已经算是用过发布-订阅模式啦!想一想是不是和售楼处的这个例子很相似:

  • 我们需要在一定条件下干一些事情
  • 但我们不知道的是这个条件会在什么时间点成立
  • 所以我们留下我们的函数
  • 当条件成立时自动执行

那么我们就来简单的模拟一下addEventListener发生的事情以便于大家理解发布-订阅模式

class DOM {#eventObj = {click: [],mouseover: [],mouseout: [],mousemove: [],keydown: [],keyup: []// 还有很多事件类型就不一一写啦}addEventListener (event, fn) {this.#eventObj[event].push(fn)}removeEventListener (event, fn) {const arr = this.#eventObj[event]const index = arr.indexOf(fn)arr.splice(index, 1)}click () {this.#eventObj.click.forEach(fn => fn.apply(this))}mouseover () {this.#eventObj.mouseover.forEach(fn => fn.apply(this))}// 还有很多事件方法就不一一写啦
}

我们来用一下试试:

const dom = new DOM()dom.addEventListener('click', () => console.log('点击啦!'))
dom.addEventListener('click', function () { console.log(this) })dom.addEventListener('mouseover', () => console.log('鼠标进入啦!'))
dom.addEventListener('mouseover', function () { console.log(this) })// 模拟点击事件
dom.click() // 依次打印出:'点击啦!' 和相应的 this 对象// 模拟鼠标事件
dom.mouseover() // 依次打印出:'鼠标进入啦!' 和相应的 this 对象const fn = () => {}
dom.addEventListener('click', fn)
// 还可以移除监听
dom.removeEventListener('click', fn)

通过这个简单的案例应该就能够明白发布-订阅模式了吧?

我们来引用一下《JavaScript设计模式与开发实践》发布-订阅模式总结出来的三个要点:

  1. 首先要指定好谁充当发布者(比如售楼处)在本例中是 dom 这个对象
  2. 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册)在本例中是 dom.#eventObj
  3. 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,挨个发短信)

记住这三个要点后,再来看一眼尤大的代码,看是不是符合这仨要点:

  • 发布者:dep 对象
  • 缓存列表:dep.subscribers
  • 发布消息:dep.notify()

所以这是一个典型的发布-订阅模式

增强版

尤雨溪的第一版代码实现的还是有些过于简陋了,首先用起来就很不方便,因为我们每次定义数据时都需要这么手写一遍gettersetter、手动的去执行一下依赖收集函数以及触发的函数。这个部分显然是可以继续进行封装的,那么再来看一眼尤雨溪实现的第二版:

let activeEffectclass Dep {subscribers = new Set()depend() {if (activeEffect) {this.subscribers.add(activeEffect)}}notify() {this.subscribers.forEach(effect => effect())}
}function watchEffect(effect) {activeEffect = effecteffect()activeEffect = null
}function reactive(raw) {// 使用 Object.defineProperty// 1. 遍历对象上存在的 keyObject.keys(raw).forEach(key => {// 2. 为每个 key 都创建一个依赖对象const dep = new Dep()// 3. 用 getter 和 setter 重写原对象的属性let realValue = raw[key]Object.defineProperty(raw, key, {get() {// 4. 在 getter 和 setter 里调用依赖对象的对应方法dep.depend()return realValue},set(newValue) {realValue = newValuedep.notify()}})})return raw
}

可以看到这一版实现的就比上一版好多了,而且感觉尤雨溪在写这一版代码时比上一版更加认真。因为这版代码里有着详细的注释,所以肯定是认真讲解的一段代码。只不过原来的注释都是用英文写的,我给它翻译成了中文。

不过各位看官请放心,除了注释被我翻译成了中文以外,其他的地方我一个字母都没有动过,就连空格都是保持的原汁原味的缩进,为的就是能够让大家看到的是尤雨溪的一手代码

尤大:怎么还生啃源码呢?我这就亲手给你写个丐版Vue相关推荐

  1. 深入解析 Vue 的热更新原理,尤大是如何巧用源码中的细节?

    大家都用过 Vue-CLI 创建 vue 应用,在开发的时候我们修改了 vue 文件,保存了文件,浏览器上就自动更新出我们写的组件内容,非常的顺滑流畅,大大提高了开发效率.想知道这背后是怎么实现的吗, ...

  2. 和尚啃源码 之 RM深大开源RP_Infantry_Plus

    和尚啃源码 之 RM深大开源RP_Infantry_Plus 闲来垂钓碧溪上.这回遇到硬茬了,任务是啃完全套RM代码,想必是一场恶战,记录一下笔记: 一.万事万物从Readme开始 1.功能介绍 本套 ...

  3. 安卓期末大作业——汉服社区(源码+任务书)

    安卓期末大作业--汉服社区(源码+任务书) 课题的作用和意义 汉族占中国人口的91%,是中国的主体民族,在越来越全球化的今天,我们正越来越多地,在不同场合,与不同的民族打交道,由此也就越是需要民族身份 ...

  4. 【商业源码】生日大放送-Newlife商业源码分享

    今天是农历六月二十三,是@大石头的生日,记得每年生日都会有很劲爆的重量级源码送出,今天Newlife群和论坛又一次疯狂了,吃水不忘挖井人,好的东西肯定要拿到博客园分享.Newlife组件信息: 论坛: ...

  5. 计算机科学期末网页大作业快看漫画源码(纯享免费版)需要自取免费配置环境

    计算机科学期末网页大作业快看漫画源码(纯享免费版)需要自取免费配置环境 我的页面 我的 </div><div class="flex-grow-1">< ...

  6. 尤雨溪的5KB petite-vue源码解析

    写在开头 近期尤雨溪发布了5kb的petite-vue,好奇的我,clone了他的源码,给大家解析一波. 最近由于工作事情多,所以放缓了原创的脚步!大家谅解 想看我往期手写源码+各种源码解析的可以关注 ...

  7. 2020大数据学习资料,全套源码无加密网盘下载

    点击上方蓝字"优派编程"选择"加为星标",第一时间关注原创干货 前言--–2020最新大数据完整版,全套源码无加密网盘下载 java大数据: 大数据(big d ...

  8. 计算机毕业设计JAVA派大星水产商城mp4mybatis+源码+调试部署+系统+数据库+lw

    计算机毕业设计JAVA派大星水产商城mp4mybatis+源码+调试部署+系统+数据库+lw 计算机毕业设计JAVA派大星水产商城mp4mybatis+源码+调试部署+系统+数据库+lw 本源码技术栈 ...

  9. 【百套源码】HTML5期末大作业 - 各类网页作业源码合集

    文章目录 持续更新文章记录 1️⃣ 个人介绍类相关源码 1.1 html实现个人简历 1.2 科技风个人简历 1.3 网站风个人简历 1.4 多种风格个人主页模板 1.5 html好看的个人简历明星版 ...

最新文章

  1. 张祥雨团队最新工作:用于物体检测的实例条件知识蒸馏 | NeurIPS 2021
  2. 题目1:学生成绩档案管理系统(代码实现)
  3. Windows消息机制(MFC)
  4. 人工智能之语音识别技术(三)
  5. DAY97 - Rest Framework(二)- 序列化组件之Serializer和ModelSerializer
  6. Android Lint 去除重复资源 之 idea探究
  7. 高端服务器CPU详细图解
  8. ezdpl Linux自动化部署实战
  9. 计算机网络原理梳理丨清晰认识 TCP/IP 协议,图解秒懂!
  10. 计算机指令格式_计算机科学组织| 指令格式
  11. 英特尔的务实创新之路:实在做技术,赋能开发者 | InfoQ推荐
  12. MUI框架开发HTML5手机APP(一)--搭建第一个手机APP(转)
  13. 图像扩充边界_使用机器学习来索引数十亿图像中的文本
  14. 【Oracle学习】archivelog
  15. vue 中使用echart5.2发生TypeError: Cannot read properties of undefined (reading ‘init‘)
  16. 设置Webdriver启动chrome为默认用户的配置信息
  17. 科技兴国园区兴城——2019国际高科技产业园区博览会在深盛装开幕
  18. fastadmin调用发送邮件验证码接口
  19. socket 长链接linux,手把手教你写 Socket 长连接
  20. Vue.js 实战总结

热门文章

  1. mybatis-generator自动生成数据库字段注释,类注释
  2. python 字符串比较时间_Python日期字符串比较
  3. iOS开发-键盘样式风格有关设置
  4. oracle增加字段为主键自增_oracle 自增序列实现 可作为主键
  5. react项目引入scss
  6. 芯片量产测试常用“黑话”
  7. 【python算法系列四】堆排序算法
  8. 《Linux 后台开发命令300》目录(更新 ing)
  9. ORACLE集群管理-19c RAC ipv6+IPV4双栈配置实战
  10. 冬天皮肤干燥,如何保养?