vue是怎么初始化数据并挂载的?
创始人
2024-03-17 14:15:17
0

vue初始化流程----模板渲染(挂载)

  1. $mount() 方法

    工程源码: src/platforms/web/entry-runtime-with-compiler.js

    1. 如果传入的 el 是字符串就查找 DOM 元素, 如果有就返回, 没有就警告并返回一个新创建的 div
    2. 如果传入的 el 不是字符串就直接返回 el (可以测试传入真实 DOM 元素 box)
    3. 然后调用了 mount() 方法并返回
    Vue.prototype.$mount = function (el) {// 根据用户传入的 el 属性获取节点el = el && query(el)let vm = this;//把节点放在 vm.$el 上方便后面使用vm.$el = el;let options = vm.$options;let template/*** 编译权重:* 优先看有没有 render 函数, 如果有就直接用* 如果没有 render 函数就看有没有 template 模板* 如果都没有就直接获取 el 的 outerHTML 作为渲染模板*/if (!options.render) {if (!options.template) {template = el.outerHTML} else {template = vm.$options.template}}if (template) {//用 template 生成 render 函数let render = compileToFunctions(template)options.render = render}//调用 mount 方法开始渲染页面。return mount(this, el)
    }
    
    • 上面代码主要实现了 Vue 渲染过程中很重要的一步: 生成 render 函数
    • 如果我们使用的 template 进行编写 HTML 代码, Vue 内部会把模板编译成 Vue 可识别的 render 函数, 如果有写 render 则省去编译过程
    • 总结: 直接写 render 函数比在 template 中写代码的编译效率更高

    query 方法

    function query (el) { // 传入了 `#box` 字符串if (typeof el === 'string') {var selected = document.querySelector(el);if (!selected) {warn('Cannot find element: ' + el);return document.createElement('div')}return selected} else {return el}
    }
    
  2. mountComponent()

    工程源码: src/core/instance/lifecycle.js

    1. 开始准备挂载真实 DOM, 触发 beforeMount 钩子
    2. 创建渲染 Watcher, 渲染 Watcher 内部调用了 updateComponent 方法
    3. 真实 DOM 渲染完毕后触发 mounted 钩子
    function mountComponent (vm, el) {// 渲染之前调用 beforeMount 生命周期callHook(vm, 'beforeMount')// 定义一个更新渲染函数 (用来获取虚拟 DOM 后渲染真实 DOM)let updateComponent = () => {// 整个渲染周期最关键的一行vm._update(vm._render())}// 生成一个渲染 Watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染new Watcher(vm, updateComponent, () => {},{before () {callHook(vm, 'beforeUpdate')}}, true)// 渲染真实 dom 结束后调用 mounted 生命周期callHook(vm, 'mounted')
    }
    
  3. Watcher()

    工程源码: src/core/observer/watcher.js

    • Vue 初次渲染时 Watcher 内部调用了 updateComponent 方法 (数据添加依赖我们后面说)
    export class Watcher {constructor(vm,expOrFn,cb,options) {if (typeof expOrFn === 'function') {// 将 updateComponent 方法赋值给 getterthis.getter = expOrFn}this.get();}get() {pushTarget(this)let value// 使用 call 调用 updateComponent 方法value = this.getter.call(this.vm, this.vm);popTarget()return value}
    }
    
  4. 触发 updateComponent() 方法, 内部的 vm.update(vm._render())就会执行

    工程源码: src/core/instance/render.js

    先看 _render()方法

    • 取出 render() 函数
    • 调用 render() 函数, 获取虚拟 DOM 后返回
    Vue.prototype._render = function () {let vm = this// 取出 render 函数let render = vm.$options.render;// 调用 render 函数得到虚拟 DOMreturn render.call(vm)
    }
    
  5. vm.update()

    工程源码: src/core/instance/lifecycle.js

    • 获取旧的虚拟 DOM
    • 如果没有说明首次渲染, 调用 patch()传入根元素 #box渲染
    • 如果有就说明是数据更新, 调用 patch()传入旧的虚拟 DOM 和新的虚拟 DOM 进行 diff 对比更新
    Vue.prototype._update = function (vnode) {let vm = this// 获取到上一次的虚拟 DOM 用于 diff 对比const prevVnode = vm._vnodeif (!prevVnode) {// 没有上次的虚拟 DOM, 说明是首次渲染vm.$el = patch(vm.$el, vnode)} else {// 有虚拟 DOM 说明是数据更新驱动视图更新vm.$el = patch(prevVnode, vnode)}// 保留虚拟 DOMvm._vnode = vnode
    }
    
  6. patch()

    工程源码: src/core/vdom/patch.js

    • 首次渲染会直接创建真实 DOM 并返回
    • 如果有子元素会递归创建子元素
    • 根据虚拟 DOM 记录的标签名 / 注释 / 文本节点来创建真实 DOM 元素
    return function patch(el, vnode, hydrating, removeOnly) {// 判断有没有旧的虚拟 DOM 如果没有就进入 ifif (isUndef(oldVnode)) {isInitialPatch = truecreateElm(vnode, insertedVnodeQueue)}// ... 省略其他不重要的代码 ...return vnode.elm
    }function createElm (vnode, // 虚拟dominsertedVnodeQueue,parentElm, // 父节点
    ) {// 查看元素 tag 是不是组件, 如果是组件就 return 不走这里, 去创建组件if (createComponent(vnode, insertedVnodeQueue, parentElm)) {return}const data = vnode.data // 获取 data 数据const children = vnode.children // 获取子元素const tag = vnode.tag // 获取标签名if (isDef(tag)) {// 创建真实 DOMvnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode)// 如果有子节点就递归创建子节点createChildren(vnode, children, insertedVnodeQueue)// 给父元素插入子元素insert(parentElm, vnode.elm, refElm)} else if (isTrue(vnode.isComment)) {// 创建注释节点vnode.elm = nodeOps.createComment(vnode.text)// 给父元素插入注释节点insert(parentElm, vnode.elm, refElm)} else {// 创建文本节点vnode.elm = nodeOps.createTextNode(vnode.text)// 给父元素插入文本节点insert(parentElm, vnode.elm, refElm)}
    }function createChildren (vnode, children, insertedVnodeQueue) {if (Array.isArray(children)) {for (let i = 0; i < children.length; ++i) {// 创建子节点createElm(children[i], insertedVnodeQueue, vnode.elm)}}
    }
    

总结

  • 直接写 render 函数比在 template 中写代码编译效率更高
  • render() 函数是用来创建虚拟 DOM 的
  • _update 中调用的 patch() 函数才是真正将虚拟 DOM 转成真实 DOM 的方法

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...