鄢栋,微医云服务团队前端工程师。有志成为一名全栈开发工程师甚至架构师,路漫漫,吾求索。生活中通过健身释放压力,思考问题。

目前 Vue3.0 打的很火热,都已经出了很多 Vue3.0 源码解析系列的博客, 但是 Vue2.0 的源码我觉得还是有必要细品一下, 掌握了原有通用的源码原理,才能知道新版本的 Vue3.0 到底做了哪些更改。如果已经很熟悉了,可跳过~

首先整体看一下整个页面渲染的流程图, 顺着这张图我们再带着问题深入研究, 相信很快就能攻克阅读 Vue 源码的困难。


初始化及挂载

new Vue() -> $mount


从文件夹 core/index.js 入口,看到 import Vue from './instance/index'这句话;

接着我们定位到 instance/index.js 文件,看到import { initMixin } from './init'

function Vue (options) {  if (process.env.NODE_ENV !== 'production' &&    !(this instanceof Vue)  ) {    warn('Vue is a constructor and should be called with the `new` keyword')  }  this._init(options)}initMixin(Vue);  // 这个 Vue 参数在当前文件定义了

可以看到:上面 Vue 构造函数中,执行了this._init(options)。this 是指当前的 Vue 实例,是从initMixin()函数中定义的。

我们定位到 instance/init.js 可以看到:

export function initMixin (Vue: Class) {  Vue.prototype._init = function (options?: Object) {    const vm: Component = this    ... // 省略中间的处理    vm._self = vm    initLifecycle(vm) // 初始化生命周期    initEvents(vm) // 初始化事件    initRender(vm) // 初始化 render    callHook(vm, 'beforeCreate')    initInjections(vm) // resolve injections before data/props    initState(vm) // 初始化 props、 methods、 data、 computed 与 watch 等    initProvide(vm) // resolve provide after data/props    callHook(vm, 'created')    ... // 省略一部分处理    if (vm.$options.el) {      vm.$mount(vm.$options.el) // 初始化之后调用 $mount 挂载组件    }  }}
  • 在 new Vue() 之后, Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会:

    • 初始化生命周期:initLifecycle(vm)
    • 初始化事件:initEvents(vm)
    • 初始化 props、 methods、 data、 computed 与 watch 等选项: initState(vm)
export function initState (vm: Component) {  vm._watchers = []  const opts = vm.$options  if (opts.props) initProps(vm, opts.props) // props  if (opts.methods) initMethods(vm, opts.methods) // methods  if (opts.data) {    initData(vm) // data  } else {    observe(vm._data = {}, true)  }  if (opts.computed) initComputed(vm, opts.computed) // computed  if (opts.watch && opts.watch !== nativeWatch) {    initWatch(vm, opts.watch) // watch  }}
  • 其中最重要的是通过 Object.defineProperty 设置 setter 与 getter 函数,用来实现「响应式」以及「依赖收集」,后面会详细讲到,这里只要有一个印象即可。

(Q1: Vue 的响应式以及依赖收集是如何实现的?)

  • 初始化之后调用 $mount 会挂载组件。

  • 如果是运行时编译,即不存在 render function 但是采用 template 进行渲染 的情况,需要进行「编译」步骤。

(Q2: Vue 的模板编译过程?)

编译

compile 编译可以分成 parse、optimize 与 generate 三个阶段,最终需要得到 render function。查看 compiler/index.js 文件:

import { parse } from './parser/index'import { optimize } from './optimizer'import { generate } from './codegen/index'import { createCompilerCreator } from './create-compiler'

// `createCompilerCreator` allows creating compilers that use alternative// parser/optimizer/codegen, e.g the SSR optimizing compiler.// Here we just export a default compiler using the default parts.export const createCompiler = createCompilerCreator(function baseCompile (  template: string,  options: CompilerOptions): CompiledResult {  const ast = parse(template.trim(), options)  if (options.optimize !== false) {    optimize(ast, options)  }  const code = generate(ast, options)  return {    ast,    render: code.render,    staticRenderFns: code.staticRenderFns  }})

可以看到,先创建了一个编译器, 创建成功后:

  • 调用parse函数生成 AST 抽象语法树;
  • 如果选项中的 optimize 为 true, 则需要进行优化, 调用optimize函数;
  • 接着, 根据生成的 AST,通过调用generate函数生成代码段对象;
  • 最后将AST、code 对象中的render 字符串(VNode 渲染所需要的) 、code 中的staticRenderFns 字符串包裹成一个对象返回。

parse

parse 会用正则等方式解析 template 模板中的指令、class、style 等数据,形成 AST。(如何解析?)

optimize

optimize 的主要作用是标记 static 静态节点,这是 Vue 在编译过程中的一处优化,后面当 update 更新界面时,会有一个 patch的过程,diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了 patch 的性能。

(Q3: Vue 是如何区分静态节点的?Vue 的 patch 过程?diff 算法做了哪些事情?)

generate

generate 是将 AST 转化成 render function 字符串的过程(如何转换?),得到结果是 render 的字符串以及 staticRenderFns 字符串。

在经历过 parse、optimize 与 generate 这三个阶段以后,组件中就会存在渲染 VNode 所需的 render function 了。

(Q4: VNode 是什么?)

响应式


当 render function 被渲染的时候,会读取对象中的值, 从而触发getter 函数进行依赖收集。依赖收集的目的是将观察者 Watcher 对象放到订阅者 Dep 中的 subs 中。

当修改对象中的值时,会触发setter 函数通知之前收集的 Dep 中的每一个 Watcher 重新渲染视图,Watcher 收到通知后, 调用 update 函数来更新视图。当然这中间还有一个 patch 的过程以及使用队列来异步更新的策略,这个我们后面再讲。


(Q5: Vue2.0 的响应式原理?)

Virtual DOM

虚拟 DOM 其实是 render function 执行后的产物,是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。

更新视图

  • 前面说到:在修改一个对象值的时候,会通过 setter -> Watcher -> update 的流程来修改对象对应的值。

  • 当数据变化后,执行 render function 就可以得到一个新的 VNode 节点,我们如果想要得到新的视图,最简单粗暴的方法就是直接解析这个新的 VNode 节点,然后用 innerHTML 直接全部渲染到真实 DOM 中。但是其实我们只对其中的一小块内容进行了修改,这样做似乎有些浪费。

  • 因此我们可以只修改有修改的部分,这个时候就会通过 patch 去比较了。将新的 VNode 与旧的 VNode 一起传入 patch 进行比较,经过 diff 算法得出它们的「差异」。最后我们只需要将这些「差异」的对应 DOM 进行修改即可。

总结

回过头来,我们再来看第一张图:对于 Vue 整体上的执行机制是否有了一些概念?

  • 页面渲染:new Vue() -> init() -> $mount()
  • 数据更新:用户操作导致数据需要更新,视图需要更新 -> getter 收集依赖 -> dep.depend() -> dep.subs.push(watcher) -> setter 通知 watcher 更新视图 -> dep.notify() -> dep.subs[i].update() -> render fucntion() -> VNode -> patch -> DOM
  • 模板更新:模板编译:parse -> optimize -> generator

具体的有些机制细节,本系列会一一更新, 共同学习, 共同进步!具体有些细节, 可能笔者在理解上存在误解,如果有问题,欢迎在评论或者留言区进行反馈交流~

参考资料

  • 染陌掘金小册《剖析 Vue.js 内部运行机制》
  • Vue2.0 源码(https://github.com/Vuejs/cn.Vuejs.org)

vue修改节点class_Vue2.0 源码解读系列 来自 Vue 的神秘礼盒相关推荐

  1. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking...

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  2. Android6.0源码解读之ViewGroup点击事件分发机制

    本篇博文是Android点击事件分发机制系列博文的第三篇,主要是从解读ViewGroup类的源码入手,根据源码理清ViewGroup点击事件分发原理,明白ViewGroup和View点击事件分发的关系 ...

  3. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  4. Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)

    Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager) 本篇主要讲解iOS开发中的网络监控 前言 在开发中,有时候我们需要获取这些信息: 手机是否联网 ...

  5. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

  6. Alamofire源码解读系列(五)之结果封装(Result)

    本篇讲解Result的封装 前言 有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解.在Alamofire中,使用Response来描述请求后的结果.我们都知道Alamof ...

  7. 【注意力机制集锦】Channel Attention通道注意力网络结构、源码解读系列一

    Channel Attention网络结构.源码解读系列一 SE-Net.SK-Net与CBAM 1 SENet 原文链接:SENet原文 源码链接:SENet源码 Squeeze-and-Excit ...

  8. Alamofire源码解读系列(十一)之多表单(MultipartFormData)

    本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...

  9. py-faster-rcnn源码解读系列

    转载自: py-faster-rcnn源码解读系列(一)--train_faster_rcnn_alt_opt.py - sunyiyou9的博客 - 博客频道 - CSDN.NET http://b ...

最新文章

  1. vue的基本项目结构
  2. arm 交叉编译找不到so_嵌入式杂谈之交叉编译
  3. MySQL 数据库恢复
  4. flutter 底部动画导航栏
  5. Databricks文档01----Azure Databricks初探
  6. python设置字符串的格式_Python中的字符串格式
  7. CentOS下安装JDK6u30
  8. Java 注解Annotation总结二
  9. python水仙花数的代码_使用python求水仙花数的代码
  10. 7、网友问答之ASCII字符传转换为数值-------------labview宝典
  11. pandas获取全部列名_pandas获取全部列名_pandas DataFrame数据重命名列名的几种方式...
  12. Word增加和删除行号
  13. Python爬虫从入门到精通——爬虫实战:爬取猫眼电影排行Top100
  14. Lua的安装配置出现的问题以及解决方案(Win10环境下)
  15. Three.js盖房子 点击开关门
  16. 实战攻防演之阻击CSRF威胁
  17. js开发html5游戏,JS开发HTML5游戏《神奇的六边形》(五)
  18. 必应搜索引擎怎么了?
  19. 上传artifacts到maven仓库
  20. 贷后评分模型的三种细分应用

热门文章

  1. torch.squeeze()和unsqueeze()
  2. JScrollPane 双滚动条
  3. Win7下U盘安装Ubuntu14.04双系统
  4. Windows7-win10开启IPv6亲测最有效方法
  5. 桁架机器人运动视频_桁架机器人的直线定位单元
  6. android相机保存文件为空,相机不保存到指定的文件位置android
  7. 语言 物品竞拍系统_【优加答疑】没有语言的孩子,该如何沟通?
  8. java虚拟机 什么语言_什么是Java虚拟机?为什么Java被称为平台无关的编程语言...
  9. 需要氪金吗_《天堂2:血盟》到底需不需要氪金?玩家:可以但是没有必要
  10. tomcat升级_「shell脚本」懒人运维之自动升级tomcat应用(war包)