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

根据调试工具看Vue源码之虚拟dom(二)

前言

上回我们提到,在子组件存在的情况下,父组件在执行完created钩子函数之后生成子组件的实例,子组件执行created钩子函数,同时也检查是否也有子组件,有则重复父组件的步骤,否则子组件的dom元素渲染

深入了解vnode

在上一篇文章中其实我们提到一个函数 —— createComponentInstanceForVnode?

function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent // activeInstance in lifecycle state
) {
  var options = {
    _isComponent: true,
    _parentVnode: vnode,
    parent: parent
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  return new vnode.componentOptions.Ctor(options)
}
复制代码

与之相关的代码?

...
var child = vnode.componentInstance = createComponentInstanceForVnode(
    vnode,
    activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
复制代码

从中我们可以得知:

  • 子组件的实例是由createComponentInstanceForVnode生成的
  • 上面的结论与vnode.componentOptions.Ctor(options)有关

VNode

通过全局检索componentOptions,可知存在如下代码?

var VNode = function VNode (
  tag,
  data,
  children,
  text,
  elm,
  context,
  componentOptions,
  asyncFactory
) {
  this.tag = tag;
  this.data = data;
  this.children = children;
  this.text = text;
  this.elm = elm;
  ...
}
复制代码

实际上,在beforeMount钩子和mounted钩子之间,有段奇怪的代码?

new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
复制代码

看过前面的文章的你其实已经知道Watcher的执行逻辑:

  1. 初始化相关属性,其中包括getter属性
  2. value赋值的同时执行getter

updateComponent实现:

updateComponent = function () {
    vm._update(vm._render(), hydrating);
};
复制代码

这意味着函数 updateComponent 将被执行,同时存在这样的调用顺序(从上往下执行):

  • vm._render
  • vm_update

同时dom元素肯定也是在这两个函数调用时渲染

vm._render

Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      );
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode;
    // render self
    var vnode;
    try {
      // There's no need to maintain a stack becaues all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm;
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      handleError(e, vm, "render");
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
        } catch (e) {
          handleError(e, vm, "renderError");
          vnode = vm._vnode;
        }
      } else {
        vnode = vm._vnode;
      }
    } finally {
      currentRenderingInstance = null;
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0];
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        );
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };
复制代码

简单梳理下函数 _render 的执行过程(从上往下):

  • _parentVnode(父组件的 vnode) 赋值给 vm.$vnode
  • 执行 normallizeScopedSlots,将父子组件的 $slots$scopedSlots 合并
  • 执行 render 函数并赋值给 vnode(即得到现有的 vnode
  • 如果 vnode 为空则执行 createEmptyVNode 函数
  • 返回 vnode

这里我们优先把断点打入 render 函数,理所当然的会得到以下执行过程:

  • render
  • vm.$createElement

由于最先执行的是 new Vue({...}),所以看上去断点好像停在了「奇怪的地方」?

new Vue({
  render: h => h(App),
}).$mount('#app')
复制代码

render 函数

细心的同学会注意到这样一行代码?

vnode = render.call(vm._renderProxy, vm.$createElement);
复制代码

步进之后断点马上跳到了这里?

new Vue({
    render: h => h(App),
    ...
}).$mount('#app')
复制代码

其实,这里将 vm._renderProxy 作为了 render 函数的上下文对象,而 vm.$createElement 返回一个闭包函数作为 render 函数的参数传入

相关代码:

vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
复制代码

总结

总结下生成 vnode 的完整逻辑:

  • 执行 $mount 函数
  • 判断是否在浏览器环境下,是则获取 dom 元素并赋值给 el 变量,否则 el 变量取值 undefined
  • 执行 mountComponent 函数
  • 执行 new Watcher(vm, updateComponent, noop, ...)
  • 由于 Watcher 的「特性」(传入的 updateComponent 赋值给 getter 之后执行),_render 函数在这之后会被触发
  • 执行 $createElement
  • 执行 createElement
  • 执行 _createElement
  • 判断参数 datadata.is 是否不为空,是则将 data.is 赋值给 tag
  • 如果 tag 为空,那么认为这是一个空白节点,此时调用 createEmptyVNode 创建一个「空白节点」,并把 isComment 标记为 true
  • 判断 tag 是否是「保留字」,是则属于 HTML 标签,生成对应的 vnode,否则调用 createComponent 函数生成对应的 vnode
  • 最后返回 vnode

相关函数

createEmptyVNode

var createEmptyVNode = function (text) {
  if ( text === void 0 ) text = '';

  var node = new VNode();
  node.text = text;
  node.isComment = true;
  return node
};
复制代码

扫描下方的二维码或搜索「tony老师的前端补习班」关注我的微信公众号,那么就可以第一时间收到我的最新文章。

转载于:https://juejin.im/post/5cd44acc6fb9a032435dc1e4

相关文章:

  • Linux基础命令---lftp连接ftp服务器
  • 项目管理(二)- 规划项目
  • 雷林鹏分享:YII 模型-视图-控制器(MVC)设计模式
  • 你是零基础,该如何经营好一个网站
  • cordova 项目打包开发
  • XMLHttpRequest对象用法
  • python while/for
  • 森拓气液增压缸什么时候更换液压油_具体步骤
  • Entity Framework 丢失数据链接的绑定,在已绑好的EDMX中提示“Choose Your Data Connection”...
  • Mybatis3.x与Spring4.x整合
  • VRSCANS改变了PX集团的汽车渲染
  • Codeforces 535B Tavas and SaDDas 数位DP
  • 分布式事务
  • C# 深浅复制 MemberwiseClone
  • poj_1741——树的分治
  • .pyc 想到的一些问题
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • C学习-枚举(九)
  • EOS是什么
  • js操作时间(持续更新)
  • Laravel 中的一个后期静态绑定
  • Swoft 源码剖析 - 代码自动更新机制
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • Transformer-XL: Unleashing the Potential of Attention Models
  • vue的全局变量和全局拦截请求器
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 让你的分享飞起来——极光推出社会化分享组件
  • 如何进阶一名有竞争力的程序员?
  • 使用Gradle第一次构建Java程序
  • MyCAT水平分库
  • ###项目技术发展史
  • #1015 : KMP算法
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (04)odoo视图操作
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (HAL库版)freeRTOS移植STMF103
  • (zhuan) 一些RL的文献(及笔记)
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (小白学Java)Java简介和基本配置
  • (正则)提取页面里的img标签
  • (转)jdk与jre的区别
  • .bat批处理(一):@echo off
  • .net web项目 调用webService
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .Net(C#)常用转换byte转uint32、byte转float等
  • @SentinelResource详解
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [BZOJ4337][BJOI2015]树的同构(树的最小表示法)
  • [C++]Leetcode17电话号码的字母组合
  • [Cloud Networking] Layer Protocol (continue)