上一篇是mount之前的添加一些方法,包括全局方法gloal-api,XXXMixin,initXXX,然后一切准备就绪,来到了mount阶段,这个阶段主要是

  • 解析template
  • 创建watcher并存入Dep
  • 更新数据时更新视图

Vue源码里有两个mount

  • 第一个
// src/platform/web/runtime/index.js

Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
// 核心方法,往下看
return mountComponent(this, el, hydrating)
}
  • 第二个
// src/platforms/web/entry-runtime-with-compiler.js

// 把上面的第一个取出来,在最后一行执行
const mount = Vue.prototype.$mount
// 把原本的替换掉,这就是第二个mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el) const options = this.$options if (!options.render) {
let template = getOuterHTML(el)
if (template) {
// compileToFunctions,来自 compiler/index.js
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
// 把第一个mount执行
return mount.call(this, el, hydrating)
}
// compiler/index.js

function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
options = options || {} // 编译,核心内容
const compiled = compile(template, options)
}

第二个mount才是在init里真正被执行的,也就是在第一个mount之前先被执行,叫做compiler阶段

compiler阶段,整个阶段全程只执行一次,目的就是生成render表达式

  • parse,将templat转成AST模型树
  • optimize,标注静态节点,就是选出没有参数的固定内容的html标签
  • generate,生成render表达式
<div id="el">Hello {{name}}</div>
// compile方法就是把 html 转为 AST,效果如下
{
type: 1,
div: 'div',
attrsList: [{
name: 'id',
value: ''el
}],
attrs: [{
name: 'id',
value: ''el
}],
attrsMap: {
id: 'el'
},
plain: false,
static: false,
staticRoot: false,
children: [
type: 2,
expression: '"hello "+ _s(name)',
text: 'Hello {{name}}',
static: false
]
}
// 由上面的AST生成 render表达式,
with (this) {
return _c(
"div",
{
attrs: {id: 'el'}
},
[
_v("Hello "+_s(name))
]
)
}
// render-helpers 下 index.js

export function installRenderHelpers (target) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
// 怎么没有_c,_c在上篇笔记的initRender方法里
// _c对应元素节点、_v对应文本节点、_s对应动态文本

到这里执行第一个mount,也就是mountComponent方法

// instance/lifecycle.js

export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 挂载前,执行beforMount
callHook(vm, 'beforeMount')
let updateComponent
// 定义updateComponent,vm._render将render表达式转化为vnode,vm._update将vnode渲染成实际的dom节点
updateComponent = () => {
// 核心内容,理解为
// var A = vm._render(),生成vDom
// vm._update(A, hydrating),生成真实dom
vm._update(vm._render(), hydrating)
}
// 首次渲染,并监听数据变化,并实现dom的更新
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 挂载完成,回调mount函数
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}

看看watch构造函数

export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// 上面的函数传了true
if (isRenderWatcher) {
vm._watcher = this
}
// 当数据发生改变,_watchers会被循环更新,也就是视图更新
vm._watchers.push(this)
if (typeof expOrFn === 'function') {
this.getter = expOrFn
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
// 把expOrFn执行了,启动了初次渲染
this.value = this.get()
}
}
get () {
return this.getter.call(vm, vm)
}
}

最值得研究的patch,这个函数特别的长,下面是超简略版

// src/core/vdom/patch.js

export function createPatchFunction (backend) {
...
// Vue.prototype.__path__ = createPatchFunction()
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// 对比
console.log(oldVnode)
console.log(vnode)
// 最后返回的就是真实的可以用的dom
return vnode.elm
}
}

不管是初始化渲染还是数据更新,都是把整个页面的render表达式重新渲染生成全部的vdom,进行新旧的对比,这个方法里还有最牛逼的domDiff算法,这里就不研究了,百度很多大佬解析

上面出现的几个重要的方法

  • _render函数主要执行compiler阶段,最后返回vDom
  • patch,在core/vdom/patch.js里,主要功能将对比新旧vDom转换为dom节点,最后返回的就是dom
  • _update主要是当数据改变时调用了patch函数

vue的整个实现流程

  • 给Vue函数添加很多的方法【Global-api,XXXMixin】
  • 对参数进行解析和监听【initXXX】
  • 启动mount阶段
  • _render函数把模版解析成AST,再解析成vnode
  • 初次渲染,执行_update,实际执行的是patch方法,patch将vDom渲染成DOM,初次渲染完成
  • data属性变化,_render通过AST再次生成新的vDom,通过_update里的patch进行对比,渲染到html中

最好的调试方法是下载vue.js文件,不要压缩版的,不用脚手架,然后在js里打断点就行

Vue源码(下篇)的更多相关文章

  1. 大白话Vue源码系列(02):编译器初探

    阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...

  2. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  3. 大白话Vue源码系列(03):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  4. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  5. 入口文件开始,分析Vue源码实现

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  6. 入口开始,解读Vue源码(一)-- 造物创世

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  7. Vue2.x源码学习笔记-Vue源码调试

    如果我们不用单文件组件开发,一般直接<script src="dist/vue.js">引入开发版vue.js这种情况下debug也是很方便的,只不过vue.js文件代 ...

  8. Vue 源码解读(8)—— 编译器 之 解析(上)

    特殊说明 由于文章篇幅限制,所以将 Vue 源码解读(8)-- 编译器 之 解析 拆成了上下两篇,所以在阅读本篇文章时请同时打开 Vue 源码解读(8)-- 编译器 之 解析(下)一起阅读. 前言 V ...

  9. VUE 源码学习01 源码入口

    VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...

随机推荐

  1. Bugku-CTF加密篇之 托马斯.杰斐逊

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAdYAAAKLCAYAAABL4qnuAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjw ...

  2. Django--模型管理器

    参考https://blog.csdn.net/qq_34788903/article/details/87889451 可参考视频 :  https://www.bilibili.com/video ...

  3. php用户名正则验证

    在php中,通过正则表达式对用户名进行格式验证,烦了我好久,终于找到了解决办法,在这里分享给大家. 用户名验证规则:用户名只能由数字.字母.中文汉字及下划线组成,不能包含特殊符号. <?php ...

  4. mongodb 用户指引

    维护人:陈权 一.mongodb install on linuxcurl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6 ...

  5. Vue-阻止页面回退

    1.原生js方法 <script language="javascript"> //防止页面后退 使用在vue时 挂载到mounted中 history.pushSta ...

  6. buuctf——easyjava

    虽然学过Javaweb的开发,但没好好学,所以对Javaweb了解不深 菜的真实 WEB-INF/web.xml泄露 贴一个别人的源码泄露总结ctf/web源码泄露及利用办法[总结中] WEB-INF ...

  7. C++学会STL

    1.1 泛型程序设计简介 泛型程序设计,简单地说就是使用模板的程序设计法.将一些常用的数据结构(比如链表,数组,二叉树)和算法(比如排序,查找)写成模板,以后则不论数据结构里放的是什么对象,算法针对什 ...

  8. java 协程

    协程是比线程更轻量级的程序处理单元,也可以说是运行在线程上的线程,由自己控制 1.适用于被阻塞的,且需要大量并发的场景. 2.不适用于,大量计算的多线程,遇到此种情况,更好实用线程去解决. 虽然Jav ...

  9. IKAnalyzer使用

    1.分析器 所有分析器最终继承的类都是Analyzer 1.1 默认标准分析器:StandardAnalyzer 在我们创建索引的时候,我们使用到了IndexWriterConfig对象,在我们创建索 ...

  10. Ext里dialog弹窗关闭与父窗口刷新问题总结

    1.弹出弹窗的js代码 var dlg = new J.dialog({ id: 'unDelTel', title:'缴费', page:encodeURI('<%=basePath%> ...