[fed-task-03-02]Vue源码-响应式、虚拟 DOM、模板编译和组件化

文章内容输出来源:拉勾教育大前端高薪训练营
文章内容包括:模块作业、学习笔记

简答题

1、请简述 Vue 首次渲染的过程。

Vue 首次渲染的过程:

(1)Vue 初始化:初始化 vue 的实例成员、静态成员;

(2)new Vue():初始化结束之后,调用 vue 构造函数。构造函数中调用了 this._init() ,这个方法相当于vue的入口,最终调用 vm.$mount() ;

(3)调用入口文件的 vm.$mount():这个方法主要是将模板编译成 render 函数。先判断是否传递了 render 选项,如果没有传递 render ,就把模板编译成 render 函数。这个过程是通过 compileToFunctions() 生成 render() 渲染函数 (new Function)。最后将 options.render = render;

(4)调用runtime版本中的vm.$mount():在这个方法中会重新获取 el

(5)调用 lifecycle.js 中的 mountComponent(this, el):方法中先判断是否有render选项,如果没有但是传入了模板,并且当前是开发环境的话会发送警告;触发 beforeMount ;然后定义 updateComponent ,这个方法中会调用 vm._render() 渲染虚拟dom ,调用 vm._update() 将虚拟dom转换成真实dom;然后创建 Watcher 实例,创建过程中传入了 updateComponent 会在 Watcher 内部调用 ,再调用 get() 方法;然后触发 mounted ,最终返回 vm ;

(6)watcher.get():创建完 Watcher 会调用一次 get ,get内部调用 updateComponent() ;调用 vm._render() 创建 VNode :调用实例化时用户传入的 render 或者编译 template 生成的 render ,返回 VNode ;调用 vm._update() : 内部调用了 vm.__patch__(vm.$el, vnode) 去挂载真实dom 并记录 vm.$el 。

2、请简述 Vue 响应式原理。

Vue 响应式原理其实是在 vm._init() 中完成的,调用顺序 initState() --> initData() --> observe() 。observe() 就是响应式的入口函数。

(1)observe(value):这个方法接收一个参数 value ,就是需要处理成响应式的对象;判断 value 是否为对象,如果不是直接返回;判断 value 对象是否有 __ob__ 属性,如果有直接返回;如果没有,创建 observer 对象;返回 observer 对象;

(2)Observer:给 value 对象定义不可枚举的 __ob__ 属性,记录当前的 observer 对象;数组的响应式处理,覆盖原生的 push/splice/unshift 等方法,它们会改变原数组,当这些方法被调用时会发送通知;对象的响应式处理,调用 walk 方法,遍历对象的每个属性,调用 defineReactive

(3)defineReactive:为每一个属性创建 dep 对象,如果当前属性的值是对象,再调用 observe ;定义 getter ,收集依赖,返回属性的值;定义 setter ,保存新值,如果新值是对象,调用 observe,派发更新(发送通知),调用 dep.notify() ;

(4)依赖收集:在 Watcher 对象的 get 方法中调用 pushTarget 记录 Dep.target 属性;访问 data 中的成员时收集依赖, defineReactive 的 getter 中收集依赖;把属性对应的 watcher 对象添加到 dep 的 subs 数组中;给 childOb 收集依赖,目的是子对象添加和删除成员时发送通知;

(5)Watcher:dep.notify 在调用 watcher 对象的 update() 方法时,调用 queueWatcher() ,判断 watcher 是否被处理,如果没有的话添加到 queue 队列中,并调用 flushSchedulerQueue() : 触发 beforeUpdate 钩子,调用 watcher.run() , run() --> get() --> getter() --> updateComponent ,清空上一次的依赖,触发 actived 钩子,触发 updated 钩子。

3、请简述虚拟 DOM 中 Key 的作用和好处。

Key 的作用:
主要用来在虚拟 DOM 的 diff 算法中,在新旧节点的对比时辨别 vnode ,使用 key 时,Vue 会基于 key 的变化重新排列元素顺序,尽可能的复用页面元素,只找出必须更新的DOM,最终可以减少DOM操作。常见的列子是结合 v-for 来进行列表渲染,或者用于强制替换元素/组件。

设置 Key 的好处:
(1)数据更新时,可以尽可能的减少DOM操作;
(2)列表渲染时,可以提高列表渲染的效率,提高页面的性能;

4、请简述 Vue 中模板编译的过程。

模板编译的过程:

(1)compileToFunctions(template, ...):模板编译的入口函数,先从缓存中加载编译好的 render 函数,如果缓冲中没有,则调用 compile(template, options) 开始编译;

(2)compile(template, options):先合并选项 options ,再调用 baseCompile(template.trim(), finalOptions) ;compile 的核心是合并 options ,真正处理模板是在 baseCompile 中完成的;

(3)baseCompile(template.trim(), finalOptions):先调用 parse() 把 template 转换成 AST tree ;然后调用 optimize() 优化 AST ,先标记 AST tree 中的静态子树,检测到静态子树,设置为静态,不需要在每次重新渲染的时候重新生成节点,patch 阶段跳过静态子树;调用 generate() 将 AST tree 生成js代码;

(4)compileToFunctions(template, ...):继续把上一步中生成的字符串形式的js代码转换为函数,调用 createFunction() 通过 new Function(code) 将字符串转换成函数; render 和 staticRenderFns 初始化完毕,挂载到 Vue 实例的 options 对应的属性中。


学习笔记

Vue.js 源码剖析-响应式原理

准备工作

Vue 源码获取

  • 项目地址: https://github.com/vuejs/vue
  • Fork 一份到自己的仓库,克隆到本地,可以自己写注释提交到 github
  • 为什么分析 Vue 2.6

    • 到目前为止 Vue 3.0 的正式版还没有发布
    • 新版本发布后,现有项目不会升级到 3.0 ,2.x 还有很长的一段过渡期
    • 3.0 项目地址: https://github.com/vuejs/vue-...

了解 Flow

  • 官网: https://flow.org/
  • JavaScript 的静态类型检查器
  • Flow 的静态类型检查错误是通过静态类型推断实现的
  • 文件开头通过 // @flow 或者 /* @flow */ 声明

调式设置

打包

打包工具 Rollup

  • Vue.js 源码的打包工具使用的是 Rollup , 比 webpack 轻量
  • webpack 把所有文件当做模块,Rollup 只处理 js 文件更适合在 Vue.js 这样的库中使用
  • Rollup 打包不会生成冗余的代码

设置 sourcemap

  • package.json 文件中的 dev 脚本中添加参数 --sourcemap
  • "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",

执行 dev

  • npm run dev 执行打包,用的是 rollup , -w 参数是监听文件的变化,文件变化自动重新打包

Vue的不同构建版本

  • npm run build 重新打包所有文件

术语

  • 完整版:同时包含编译器和运行时的版本
  • 编译器:用来将模板字符串编译称为 JS 渲染函数的代码,体积大、效率低
  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码,体积小、效率高、基本上就是除去编译器的代码
  • UMD:UMD 版本是通用的模板版本,支持多种模块方式。vue.js 默认文件就是运行时 + 编译器的 UMD 版本
  • CommonJS(cjs): CommonJS 版本用来配合老的打包工具比如 Browserify 或 webpack 1
  • ES Module: 从 2.6 开始 Vue 会提供两个 ES Modules 构建文件,为现代打包工具提供的版本

    • ESM 格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行 tree-shaking 并将用不到的代码排除出最终的包
    • ES6 模块与 CommonJS 模块的差异,参考 https://es6.ruanyifeng.com/?s...

寻找入口文件

  • 查看 dist/vue.js 的构建过程
  • 执行 npm run dev
  • script/config.js 的执行过程

    • 作用:生成 rollup 构建的配置文件
    • 使用环境变量 TARGET = web-full-dev

从入口开始

  • srcplatformswebentry-runtime-with-compiler.js
  • 阅读源码记录

    • el 不能是 body 或者 html 标签
    • 如果没有 render ,把 template 转换成 render 函数
    • 如果有 render 方法,直接调用 mount 挂载 DOM

Vue初始化的过程

四个导出Vue的模块

  • srcplatformswebentry-runtime-with-compiler.js

    • web 平台相关的入口
    • 重写了平台相关的 $mount() 方法
    • 注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数
  • srcplatformswebruntimeindex.js

    • web平台相关
    • 注册和平台相关的全局指令: v-model v-show
    • 注册和平台相关的全局组件: v-transition v-transition-group
    • 全局方法: __patch__: 把虚拟dom转换成真实DOM; $mount: 挂载方法
  • srccoreindex.js

    • 与平台无关
    • 设置了 vue的静态方法,initGlobalAPI(Vue)
  • srccoreinstanceindex.js

    • 与平台无关
    • 定义了狗仔函数,调用了 this._init(options) 方法
    • 给 Vue 中混入了常用的实例成员

数据响应式原理

通过查看源码解决下面的问题

  • vm.msg = { count: 0 } ,重新给属性赋值,是否是相应式的?
  • vm.arr[0] = 4 , 给数组元素赋值,视图是否会更新?
  • vm.arr.length = 0 , 修改数组的 length ,视图是否会更新
  • vm.arr.push(4) , 视图是否会更新

相应式处理的入口

  • src/core/instance/init.js

    • initState(vm) vm 状态的初始化
    • 初始化了 _data , _props , methods 等等
  • src/core/instance/state.js

Watcher 类

  • Watcher 分为三种,Computed Watcher 、用户 Watcher (侦听器)、渲染 Watcher
  • 渲染 Watcher 的创建时机: /src/core/instance/lifecycle.js

set 方法源码

  • Vue.set() global-api/index.js
  • vm.$set() instance/index.js

delete 方法源码

  • Vue.delete() global-api/index.js
  • vm.$delete() instance/index.js
  • 删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是应该很少会使用它。
  • 注意:目标对象不能是一个 Vue 实例或 Vue 实例的跟数据对象

vm.$watch

vm.$watch(expOrFn, callback, [options])

  • 观察 Vue 实例变化的一个表达式或计算属性函数。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
  • expOrFn: 要监视的 $data 中的属性,可以是表达式或函数
  • callback: 数据变化后执行的函数

    • 函数:回调函数
    • 对象: 具有 handler 属性(字符串或者函数),如果该属性为字符串则 methods 中相应的定义
  • options: 可选的选项

    • deep: 布尔类型,深度监听
    • immediate: 布尔类型,是否立即执行一次回调函数

三种类型的 Watcher 对象

  • 没有静态方法,因为 $watch 方法中要使用 Vue 的实例
  • Watcher 分三种:计算属性 Watcher、用户 Watcher(侦听器)、渲染 Watcher
  • 创建顺序:计算属性 Watcher、用户 Watcher(侦听器)、渲染 Watcher
  • vm.$watch() : src/core/instance/state.js

异步更新队列-nextTick()

  • Vue 更新 DOM 是异步执行的,批量的
  • 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
  • vm.$nextTick(function () { /*操作DOM*/ })Vue.nextTick(function () {})
  • 源码位置: src/core/instance/render.js

源码

  • 手动调用 vm.$nextTick()
  • 在 Watcher 的 queueWatcher 中执行 nextTick()
  • src/core/util/next-tick.js
  • 核心是 timerFunc 函数的处理,Promise -> MutationObserver -> setImmediate -> setTimeout

Vue.js 源码剖析-虚拟 DOM

什么是虚拟DOM

  • 虚拟 DOM (Virtual DOM) 是使用 JavaScript 对象描述真实 DOM
  • Vue.js 中的虚拟 DOM 借鉴 Snabbdom ,并添加了 Vue.js 的特性。例如:指令和组件机制

为什么要使用虚拟 DOM

  • 避免直接操作 DOM,提高开发效率
  • 作为一个中间件可以跨平台
  • 虚拟 DOM 不一定可以提高性能

    • 首次渲染的时候会增加开销
    • 复杂视图情况下提升渲染性能

h 函数

vm.$createElement(tag, data, children, normalizeChildren)

  • tag: 标签名或者组件对象
  • data: 描述 tag ,可以设置 DOM 的属性或者标签的属性
  • children: tag 中的文本内容或者子节点

设置 key 的情况

设置 Key 的好处:
(1)数据更新时,可以尽可能的减少DOM操作;
(2)列表渲染时,可以提高列表渲染的效率,提高页面的性能;

Vue.js 源码剖析-模板编译和组件化

模板编译的作用

  • Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂
  • 用户只需要编写类似 HTML 的代码 - Vue.js 模板,通过编译器将模板转换为返回 VNode 的 render 函数
  • .vue 文件会被 webpack 在构建的过程中转换成 render 函数

编译生成的函数的位置

  • _c() srccoreinstancerender.js
  • _m()/_v()/_s() srccoreinstancerender-helpersindex.js

抽象语法树

什么是抽象语法树

  • 抽象语法树简称 AST (Abstract Syntax ZTree)
  • 使用对象的形式描述树形的代码结构
  • 此处的抽象语法树是用来描述树形结构的 HTML 字符串

为什么要使用抽象语法树

  • 模板字符串转换成 AST 后,可以通过 AST 对模板做优化处理
  • 标记模板中的静态内容,在 patch 的时候直接跳过静态内容
  • 在 patch 的过程中静态内容不需要对比和重新渲染

组件化

  • 一个 Vue 组件就是一个拥有预定义选项的一个 Vue 实例
  • 一个组件可以组成页面上一个功能完备的区域,组件可以包含脚本、样式、模板

首次渲染过程

  • Vue 构造函数
  • this._init()
  • this.$mount()
  • mountComponent()
  • new Watcher() 渲染 Watcher
  • updateComponent()
  • vm._render() -> createElement()
  • vm._update()

你可能感兴趣的:(vue.js)