关于vue.js:fedtask0302Vue源码响应式虚拟-DOM模板编译和组件化

59次阅读

共计 6554 个字符,预计需要花费 17 分钟才能阅读完成。

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

简答题

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()
正文完
 0