当前位置: 首页 > news >正文

vue是怎么初始化数据并挂载的?

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 方法赋值给 getter
          this.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 函数得到虚拟 DOM
       return 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._vnode
         if (!prevVnode) {
         	 // 没有上次的虚拟 DOM, 说明是首次渲染
             vm.$el = patch(vm.$el, vnode)
         } else {
         	 // 有虚拟 DOM 说明是数据更新驱动视图更新
             vm.$el = patch(prevVnode, vnode)
         }
         // 保留虚拟 DOM
         vm._vnode = vnode
    }
    
  6. patch()

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

    • 首次渲染会直接创建真实 DOM 并返回
    • 如果有子元素会递归创建子元素
    • 根据虚拟 DOM 记录的标签名 / 注释 / 文本节点来创建真实 DOM 元素
    return function patch(el, vnode, hydrating, removeOnly) {
      // 判断有没有旧的虚拟 DOM 如果没有就进入 if
      if (isUndef(oldVnode)) {
          isInitialPatch = true
          createElm(vnode, insertedVnodeQueue)
      }
      // ... 省略其他不重要的代码 ...
      return vnode.elm
    }
    
    function createElm (
      vnode, // 虚拟dom
      insertedVnodeQueue,
      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)) {
        // 创建真实 DOM
        vnode.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 的方法

相关文章:

  • 环形链表题
  • 网络之路29:三层链路聚合
  • Spring Boot | Spring Security ( SpringBoot安全管理 )、Spring Security中 的 “自定义用户认证“
  • spring cloud eureka 初始化报错(A bean with that name has already been defined)
  • springboot权限验证学习-下
  • MR混合现实实训系统为农学情景实训教学演练
  • K8s Pod资源管理组件
  • 智慧公厕让社区生活更美好
  • Qt RGB三色灯上位机
  • 正则表达式中的特殊字符
  • dell戴尔电脑灵越系列Inspiron 15 3520原厂Win11系统中文版/英文版
  • Spring Boot基础面试问题(一)
  • MYSQL用函数请三思
  • Spring_第3章_AOP+事务
  • 手写数组方法之不改变原数组方法
  • Mockito verify Junit5集成 Mockito
  • 文件或者文件夹的忽略
  • 年产2万吨山楂酒工厂的设计-发酵工段及车间的设计(lunwen+任务书+cad图纸)
  • 【LIN总线测试】——LIN主节点调度表测试
  • 写一个flutter程序2
  • 给定一个数组arr,长度为N且每个值都是正数,代表N个人的体重。再给定一个正数 limit,代表一艘船的载重。
  • 【大道模式】状态模式 - State Pattern(审核状态流转)
  • 协议-序列化-http-Cookie-Session-https
  • 数据结构—Map集合
  • SpringMVC对消息转换器的处理相关
  • Linux-文件压缩解压
  • [附源码]计算机毕业设计JAVA医药管理系统
  • [附源码]计算机毕业设计基于SpringBoot+Vue的健身房会员系统的设计与实现
  • 9 特色聚类
  • python中的集合详解
  • pringboot面向爱宠人群的宠物资讯系统36as8计算机毕业设计-课程设计-期末作业-毕设程序代做
  • Flink系列之Flink中StateBackend深入剖析和应用