热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Vuejs源码剖析响应式原理、虚拟DOM、模板编译和组件化(23)

1.请简述Vue首次渲染过程1.实例创建完成后,调用$mount()方法完整版中会先调用srcplatformswebentry-runtime-with-compiler.js中




1.请简述Vue首次渲染过程


1.实例创建完成后,调用$mount()方法

完整版中会先调用src/platforms/web/entry-runtime-with-compiler.js中重写的$mount()(即进行模板编译),其中:


  • 先判断options中是否有render,如果没有传递render,调用compileToFunctions(),生成render()函数
  • 然后设置options.render = render

然后调用原来的$mount()(在src/platform/web/runtime/index.js中定义),其中会调用mountComponent(this, el, hydrating)


2.mountComponent(this, el, hydrating)


  • 触发beforeMount:callHook(vm, ‘beforeMount’)`
  • 定义函数updateCompOnent= () => { vm._update(vm._render(), hydrating) }
    • vm._render()生成虚拟DOM(vm._render()定义在src/core/instance/render.js中)
    • vm._update()将虚拟DOM转换成真是DOM(vm._update()定义在src/core/instance/lifecycle.js中)
  • 创建Watcher实例,把updateComponent传递进去,updateComponent是在Watcher中通过Watcherget()实例方法执行的
  • 触发mounted;返回实例return vm

3.watcher.get()

首次渲染创建的是渲染Watcher,创建完Watcher实例后会调用一个get()方法,get()中会调用updateComponent()updateComponent()中会调用vm._update(VNode. hydrating),而其中的VNode是调用vm._render()创建VNode

vm._render()的执行过程:


  • 获取创建实例时存放在options中的render函数:const { render, _parentVnode } = vm.$options
  • 调用render.call(vm.renderProxy, vm.$createElement)(这个render是创建实例new Vue()时传入的render(),或者是编译template生成的render()),最后返回VNode

然后执行vm._update(VNode, hydrating),其中:


  • 调用vm.__patch__(vm.$el, vnode)挂载真实DOM
  • vm.__patch__的返回值记录在vm.$el



2.请简述 Vue 响应式原理

Vue使用观察者模式来对其数据进行响应式处理,过程如下:


创建观察者


  • 在创建 Vue 实例时,调用的 this._init(options) 中会执行 initInjections(vm)initState(vm),这两个方法中分别会对 inject 的成员、本实例的 propsdata 进行响应式处理

  • initInjections(vm) 中会遍历 inject 的成员,通过 defineReactive(vm, key, result[key]) 将每个成员转换成响应式属性(即劫持 getter/setter

  • initState(vm) 中调用 initProps(vm, opts.props),其中编辑 props 属性,通过 defineReactive(props, key, value) 将属性转换成gettersetter,然后存入 props(也是 vm._props)中

  • initState(vm) 中调用 initData(vm, opts.props),其中调用 observe(data, true /* asRootData */)data 进行响应式处理

  • defineReactive(obj, key, val, customSetter?, shallow?)


    • 会将传入的 key 换成响应式属性,即其劫持 getter/setterObject.defineProperty( obj, key, { ..., get(){...}, set(){...} } )
    • 为每个属性 key 生成一个 Dep 对象 depconst dep = new Dep());dep 会在 getter 中收集依赖(即相应属性的 Watcher 对象),在 setter 中调用 dep.notify() 派发更新;
    • 在需要递归观察子对象时,会调用 observe(val)let childOb = !shallow && observe(val)),若 val 是个对象,则会为这个对象创建一个 Observer 对象,并返回。
  • observe (value, asRootData?)


    • 判断 value 是否是对象,不是对象就返回
    • 是对象,则这个对象可称之为 观测对象 ,然后为这个对象创建一个 Observer 对象 :ob = new Observer(value),并返回 ob(在 defineReactive 中,这个返回的 ob 会在 getter 中收集依赖相应的依赖)
    • Observer 构造函数中,会新建一个 Dep 对象 dep,这里的 dep 是为传入的 观测对象(进行响应式处理的对象)收集依赖(Watcher);与 defineReactive 中的 dep 不一样
    • Observer 实例挂载到 观测对象 的 __ob__ 属性:def(value, '__ob__', this)
    • value 不是数组,则执行 this.walk(value) 方法,遍历 value 中的每一个属性,然后调用 defineReactive(obj, keys[i])
    • value 是数组
      • Vue 并没有对数组对象的索引调用 defineReactive 来生成 getter/setter,而是重写了原生数组中会更改原数组的方法,调用这些新方法后,数组对象对应的 dep 对象会调用 dep.notify 方法来驱动视图的更新
      • 重写的数组方法:'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
      • 重写数组方法后,调用 this.observeArray(value),作用是当数组中的元素存在对象时,为数组中的每一个对象创建一个 observer 实例

至此,创建观察者结束


依赖的收集


  • 在进行挂载时( 调用 $mount() ),会执行 mountComponent 方法,其中会创建一个渲染 Watcher 对象
  • 渲染 Watcher 对象构造函数的最后会执行 get() 方法,get() 中会先执行 pushTarget(this)pushTarget 中则会将 Dep.target 设置为该 watcherDep.target = target
  • 然后调用 this.getter.call(vm, vm),即执行了 vm._update(vm._render(), hydrating),而 vm._render() 中执行 render.call(vm._renderProxy, vm.$createElement) 以生成 vnode,这个生成 VNode 的过程中,会触发 相应的响应式数据的 getter ,然后其中的 dep.depend() 则会收集当前实例 watcher

当生成完 VNode 后,就完成了响应式数据的的依赖收集


通知的发送

但修改某个响应式数据时,会触发该数据的 setter


  • 如果新值是对象,且需要递归观察子对象时执行 childOb = !shallow && observe(newVal),将新增也进行响应式处理
  • 调用 dep.notify() 派发更新,notify 会调用每个订阅者(watcher)的 update 方法实现更新
  • watcherupdate 中使用 queueWatcher() 判断 watcher 是否被处理,若没有,则把 watcher 添加进 queue 队列中,并调用 flushSchedulerQueue()
  • flushSchedulerQueue() 中先触发 beforeUpdate 钩子函数,然后调用 watcher.run()
  • watcher.run() 中会调用 get() 方法,get 中执行 getter,而 getter 就是传入的 updateComponent 方法,updateComponent 中执行 vm._update(vm._render(), hydrating),如此就完成了视图的更新
  • 然后 flushSchedulerQueue() 后续代码中 还原更新步骤的初始状态、触发 actived 钩子函数、触发 updated 钩子函数



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

Key 是用来优化 Diff 算法的。Diff算法核心在于同层次节点比较,Key 就是用于在比较同层次新、旧节点时,判断其是否相同。

Key 一般用于生成一列同类型节点时使用,这种情况下,当修改这些同类型节点的某个内容、变更位置、删除、添加等时,此时界面需要更新视图,Vue 会调用 patch 方法通过对比新、旧节点的变化来更新视图。其从根节点开始若新、旧 VNode 相同,则调用 patchVnode

patchVnode 中若新节点没有文本,且新节点和旧节点都有有子节点,则需对子节点进行 Diff 操作,即调用 updateChildrenKey 就在 updateChildren 起了大作用

updateChildren 中会遍历对比上步中的新、旧节点的子节点,并按 Diff 算法通过 sameVnode 来判断要对比的节点是否相同


  • 若这里的子节点未设置 Key,则此时的每个新、旧子节点在执行 sameVnode 时会判定相同,然后再次执行一次 patchVnode 来对比这些子节点的子节点
  • 若设置了 Key,当执行 sameVnode
    • Key 不同 sameVnode 返回 false,然后执行后续判断;
    • Key 相同 sameVnode 返回 true,然后再执行 patchVnode 来对比这些子节点的子节点

即,使用了 Key 后,可以优化新、旧节点的对比判断,减少了遍历子节点的层次,少使用很多次 patchVnode




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


Vue 模板编译入口文件执行过程

在完整版 Vue 中,src/platforms/web/entry-runtime-with-compiler.js 里先保留 Vue 实例的 mount方法,然后重写该mount方法,然后重写该mount 方法,这个重写的方法就是完整版 Vue 中的模板编译器,其中在 $options 上挂载了模板编译后生成的 render 函数。

$options 上的 render 函数是由 compileToFunctions(template, options, vm) 这个函数生成,即将 template 转换成了 render 函数。所以这里就是完成了首次加载时对模板的编译。

这里梳理下生成 render 函数的相关函数的调用过程


第一步

调用 compileToFunctions(template, options, vm)

// src/platforms/web/entry-runtime-with-compiler.js
// 把 template 转换成 render 函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns

第二步

compileToFunctions 是由 src/platforms/web/compiler/index.js 里的 createCompiler(baseOptions) 生成的。baseOptions 里是一些关于指令、模块、HTML标签相关的方法,这里不予关心。

所以 第一步compileToFunctions 是这里的 createCompiler 返回的函数。

// src/platforms/web/compiler/index.js
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }

第三步

createCompiler 来自于 src/compiler/index.js,其中调用了 createCompilerCreator(function baseCompile (template, options)) 方法

所以 第二步 中的 createCompiler 来自于这里的 createCompilerCreator 返回的函数,createCompilerCreator 中传入 函数 baseCompile 作为参数

那么 第一步 中的 compileToFunctions 就是这里的 createCompilerCreator 返回的函数执行(即执行 createCompiler(baseOptions))后返回的函数

// src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 把模板转换成 ast 抽象语法树
// 抽象语法树,用来以树形的方式描述代码结构
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 优化抽象语法树
optimize(ast, options)
}
// 把抽象语法树生成字符串形式的 js 代码
const code = generate(ast, options)
return {
ast,
// 渲染函数
render: code.render,
// 静态渲染函数,生成静态 VNode 树
staticRenderFns: code.staticRenderFns
}
})

第四步

createCompilerCreator 来自于 src/compiler/create-compiler.js

// src/compiler/create-compiler.js
export function createCompilerCreator (baseCompile: Function): Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
const finalOptiOns= Object.create(baseOptions)
....
const compiled = baseCompile(template.trim(), finalOptions)
...
return compiled
}

return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}

可以看出这个函数返回了 createCompiler(baseOptions) 函数,则往上推可知 第二步 中的

// src/platforms/web/compiler/index.js
const { compile, compileToFunctions } = createCompiler(baseOptions)

其实就是执行的这里的 function createCompiler (baseOptions: CompilerOptions){...},这个 createCompiler 函数里返回了方法 compile、compileToFunctions

方法 compile 中执行了传入的函数参数 baseCompile,这个 baseCompile第三步 中传入的,其返回值为 ast、render、staticRenderFns

而方法 compileToFunctions 正是 第一步 中调用的 compileToFunctions(template, options, vm),其来自于 createCompileToFunctionFn(compile)


第五步

createCompileToFunctionFn 来自于 src/compiler/to-function.js

// src/compiler/to-function.js
export function createCompileToFunctionFn (compile: Function): Function {
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
...

// 1. 读取缓存中的 CompiledFunctionResult 对象,如果有直接返回
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}

// 2. 把模板编译为编译对象(render, staticRenderFns),字符串形式的js代码
const compiled = compile(template, options)

// 3. 把字符串形式的js代码转换成js方法
res.render = createFunction(compiled.render, fnGenErrors)
res.staticRenderFns = compiled.staticRenderFns.map(code => {
return createFunction(code, fnGenErrors)
})

// 4. 缓存并返回res对象(render, staticRenderFns方法)
return (cache[key] = res)
}
}

createCompileToFunctionFn 返回函数 compileToFunctions,即 第四步createCompiler 函数返回的 compileToFunctions,所以是 第一步 中调用的 compileToFunctions 就是在执行这里的 compileToFunctions


总结过程


  • 执行 src/platforms/web/entry-runtime-with-compiler.js 中的 compileToFunctions(template, options, vm)
  • 执行 src/compiler/to-function.js 中的 compileToFunctionscompileToFunctions 中调用 compile(template, options)
  • 执行 src/compiler/create-compiler.js 中的 compilecompile 中调用 baseCompile(template.trim(), finalOptions)
  • 执行 src/compiler/index.js 传入 createCompilerCreator 中的函数参数 baseCompile(template, options),返回 ast、render、staticRenderFns
  • src/compiler/create-compiler.jsconst compiled = { ast、render、staticRenderFns },返回 compiled
  • src/compiler/to-function.js 中返回 res,即返回 render, staticRenderFns 方法
  • src/platforms/web/entry-runtime-with-compiler.js 获取 render、staticRenderFns


推荐阅读
  • Node.js 教程第五讲:深入解析 EventEmitter(事件监听与发射机制)
    本文将深入探讨 Node.js 中的 EventEmitter 模块,详细介绍其在事件监听与发射机制中的应用。内容涵盖事件驱动的基本概念、如何在 Node.js 中注册和触发自定义事件,以及 EventEmitter 的核心 API 和使用方法。通过本教程,读者将能够全面理解并熟练运用 EventEmitter 进行高效的事件处理。 ... [详细]
  • 在基于.NET框架的分层架构实践中,为了实现各层之间的松散耦合,本文详细探讨了依赖注入(DI)和控制反转(IoC)容器的设计与实现。通过合理的依赖管理和对象创建,确保了各层之间的单向调用关系,从而提高了系统的可维护性和扩展性。此外,文章还介绍了几种常见的IoC容器实现方式及其应用场景,为开发者提供了实用的参考。 ... [详细]
  • Java Web开发中的JSP:三大指令、九大隐式对象与动作标签详解
    在Java Web开发中,JSP(Java Server Pages)是一种重要的技术,用于构建动态网页。本文详细介绍了JSP的三大指令、九大隐式对象以及动作标签。三大指令包括页面指令、包含指令和标签库指令,它们分别用于设置页面属性、引入其他文件和定义自定义标签。九大隐式对象则涵盖了请求、响应、会话、应用上下文等关键组件,为开发者提供了便捷的操作接口。动作标签则通过预定义的动作来简化页面逻辑,提高开发效率。这些内容对于理解和掌握JSP技术具有重要意义。 ... [详细]
  • 如何在 Node.js 环境中将 CSV 数据转换为标准的 JSON 文件格式? ... [详细]
  • RxJava 中 Observable.singleElement() 方法详解与实例代码分析 ... [详细]
  • 深入探索Node.js新框架:Nest.js第六篇
    在本文中,我们将深入探讨Node.js的新框架Nest.js,并通过一个完整的示例来展示其强大功能。我们将使用多个装饰器创建一个基本控制器,该控制器提供了多种方法来访问和操作内部数据,涵盖了常见的CRUD操作。此外,我们还将详细介绍Nest.js的核心概念和最佳实践,帮助读者更好地理解和应用这一现代框架。 ... [详细]
  • 分布式开源任务调度框架 TBSchedule 深度解析与应用实践
    本文深入解析了分布式开源任务调度框架 TBSchedule 的核心原理与应用场景,并通过实际案例详细介绍了其部署与使用方法。首先,从源码下载开始,详细阐述了 TBSchedule 的安装步骤和配置要点。接着,探讨了该框架在大规模分布式环境中的性能优化策略,以及如何通过灵活的任务调度机制提升系统效率。最后,结合具体实例,展示了 TBSchedule 在实际项目中的应用效果,为开发者提供了宝贵的实践经验。 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • 利用PaddleSharp模块在C#中实现图像文字识别功能测试
    PaddleSharp 是 PaddleInferenceCAPI 的 C# 封装库,适用于 Windows (x64)、NVIDIA GPU 和 Linux (Ubuntu 20.04) 等平台。本文详细介绍了如何使用 PaddleSharp 在 C# 环境中实现图像文字识别功能,并进行了全面的功能测试,验证了其在多种硬件配置下的稳定性和准确性。 ... [详细]
  • 浅析Java泛型及其应用
    Java泛型是自JDK 5引入的一项重要特性,旨在增强代码的类型安全性和复用性。通过泛型,开发人员可以在编译阶段进行类型检查,有效避免运行时的类型转换错误。本文将探讨Java泛型的基本概念、实现机制及其在实际开发中的应用场景,帮助读者深入理解并灵活运用这一强大工具。 ... [详细]
  • 在Java编程中,为了提高代码的可读性和执行效率,建议优先使用局部变量来存储方法的返回值,而不是多次调用同一个方法。这样不仅可以减少方法调用的开销,还能避免潜在的性能问题。此外,使用局部变量还可以增强代码的可维护性和调试便利性。 ... [详细]
  • 深入解析JavaScript词法分析的具体流程与常见问题 ... [详细]
  • 本文深入探讨了 iOS 开发中 `int`、`NSInteger`、`NSUInteger` 和 `NSNumber` 的应用与区别。首先,我们将详细介绍 `NSNumber` 类型,该类用于封装基本数据类型,如整数、浮点数等,使其能够在 Objective-C 的集合类中使用。通过分析这些类型的特性和应用场景,帮助开发者更好地理解和选择合适的数据类型,提高代码的健壮性和可维护性。苹果官方文档提供了更多详细信息,可供进一步参考。 ... [详细]
  • 在运行时动态获取Entity Framework中的ObjectSet可以通过反射机制实现。这种方法允许开发者在应用程序运行期间根据需要加载不同的实体集合,从而提高代码的灵活性和可扩展性。通过使用`DbContext`类的`Set`方法,结合类型信息,可以轻松地实现这一目标。此外,还可以利用`Type`对象和泛型方法来进一步增强动态性,确保在处理多种实体类型时更加高效和安全。 ... [详细]
  • Spring Boot 实战(一):基础的CRUD操作详解
    在《Spring Boot 实战(一)》中,详细介绍了基础的CRUD操作,涵盖创建、读取、更新和删除等核心功能,适合初学者快速掌握Spring Boot框架的应用开发技巧。 ... [详细]
author-avatar
洪锐林
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有