起源
这周经历的事情有点多, 从周一的一面,到周二的二面到周三的口头offer,周四的体检, 最后再到因为某些原因没收到正式offer。
在小公司工作了两年,迫切希望能在中大型公司中工作。对于这次的失利,还是挺难受的。恰逢周末,通读了《深入浅出vue.js》,写点零零散散的记录吧。因为只是想写点东西,研究的不透彻不到位不一定正确,下文质量奇差,阅读过程请注意,请见谅。
在开始读vue源码之前,学习资料绕不开Vue.js技术揭秘和Vue技术内幕这两个文档, 巧的是这两篇源码解读都是从 new Vue()
开始引导我们逐步进入Vue源码的大门, 但从《深入浅出vue.js》中展现了另外一种思路。
先看一下vue的定义
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
加上我们平时使用的经验, 这里我思考下,vue的源码,是要实现什么功能。就像在做一个项目的时候,我们需要进行需求定义一样。
总结了一下,有些跳跃性思维:
- 发布-订阅者模式
- 虚拟DOM
- 模板编译
- 扩展方法
在我的理解中, vue源码核心就是这四件事,其他的有跨平台的包装、提供vue的构造函数和代码结构等等。
一、发布-订阅者模式
这里就不解释这个模式的定义了。我们从代码方面考虑一下实现这个模式需要哪些类和方法。
首先我们都知道,实现这个模式的原理是通过Object.defineProperty
(vue3.0要改成proxy)为data封装了get和set方法。在getter中收集依赖, 在setter中触发依赖。所以我们需要封装一个函数,或者说是封装一个类,可以拿到一个data,就把data中所有的属性都用Object.defineProperty
处理一下。这个类在vue源码中就是Observer,它的作用就是将一个数据内的所有属性(包括紫属性)都转换成getter、setter的形式,然后追踪它们的变化。
然后我们考虑一下getter中收集依赖的功能要怎么实现,在js中用来存放数据的,我们首先想到的是用数组,再考虑到getter中收集了依赖,可能还会触发其他逻辑,例如需要增删判重呀,所以我们又要封装一个类。 这个类在vue源码中就是Dep类,它专门用来帮助我们管理依赖。使用这个类,我们可以收集依赖、删除依赖或者向依赖发送通知等。
再然后我们再想一下,什么是依赖呢,不可能我在记录依赖中就记入用到data的文件名和代码行位置吧。所以我们又需要封装一个类来代表依赖。这个类在xue源码中就是Watcher类。Watcher类本质是一个中介,数据发生变化时通知他,然后它再通知其他地方。在我们自己的代码编译阶段,每当遇到一个使用data的地方, 就在那个地方new一个Watcher,至于记录位置嘛,直接把this传进去就好了呀。而且好玩的是,这new Watcher的时候,我们会触发到data的getter方法(因为我们要把数据返回给那个位置嘛),这个getter就自动把Watcher加入到Dep中了,省下了我们还需要遍历代码,再把Watcher加进Dep这个逻辑了。
这里该有张图,就随便贴一张啦= =
大概逻辑就完成了,剩下的就是打补丁,各种bug类型有:
- object的数据可以用getter/setter,array咋办,例如array可以直接push,才不会调你的setter呢。解决吧,找个拦截器,覆盖Array原型上的push、pop、shift等方法,往里一贴,干脆就在拦截器中触发依赖,bug解决,加薪。
- 有一天我往object里添加属性、删除属性,用this.list[0] = 1的方式修改数组时,发现并没有触发更新这个操作,用户怎么办?提bug!程序员怎么办?打补丁!想一想
defineProperty
确实监听不到,es6之前也不能模拟数组的原生行为,那就让用户自己多注意解决吧,给你提供一个vm.$set
,一个vm.$delete
。好了,下班。代码没bug,是你不会用。 - 一上午只想到这两,剩下还有的话大家自己发现吧。
二、虚拟DOM
首先老生常谈一下,引入虚拟DOM后,是80%的场景下提高了渲染速度,而剩下20%反而变慢了。
作为一个框架,我们不仅要考虑减少程序员的工作量,还要考虑到用户的体验方面。为了从整体上提高渲染速度,听说有个流行的概念叫virtual DOM,咱vue也要用。
虚拟DOM呢,用代码来实现,也就是一个类,在vue源码中叫做VNode类,我们用这个类来描述真实DOM元素,而且还可以扩展描述,比如说我们可以给vnode也增加分类,这个vnode叫做注释节点,那个叫文本节点,还有个叫元素节点,还有其他的这里不扩展。
有了虚拟DOM,最终还是要渲染到真实DOM上,这个过程就叫patch,这个在源码中也是大头了,但咱不具体讲了,我们先只要知道patch做的事,本质上是新增节点,删除节点和更新节点。源码中大量的代码都是尽可能的优化这个过程。
三、模板编译
vue这个逻辑也很清晰,齐步三步走:1.解析器,2.优化器,3.代码生成器。
平时我们在写.vue文件时,template中的代码看上去和html结构一样,但它为啥能实现v-for
, v-if
, {{message}}
呢,那就是因为我们看到的网页,并不是我们的template,而是被vue编译过的。
首先,逐行解析咱的代码,用的是HTML解析器和文本解析器,把代码转换成AST(抽象语法树)。
然后,对AST使用优化器优化,例如标记静态节点啥的。
最后,通过代码生成器,把AST编译成可调用的JS语句。由此就可以去生成virtual DOM了。
四、扩展方法 通过以上三个模块,这个框架就歪歪斜斜的搭建起来了。在我们不得不用jquery来写代码的时候,也可以利用上面的思路。
为了用户的体验和逻辑的完整,vue的余下代码中提供了很多方法。包括:vm.$on
,vm.$emit
,vm.$nextTick
,Vue.extend
,Vue.filter
,生命周期钩子函数等等。
总结
本文写了点对vue源码的大纲整理笔记,可食用性不高。