zoukankan      html  css  js  c++  java
  • vue 源码分析之new Vue() 的时候都发生了什么事

    疫情期间学习成果继续输出,若有不对的地方请指出,感激不尽!

    在做vue项目的时候都会运行以下这段代码,我只知道这是new了一个vue实例,然后初始化,编译,挂载,卸载,如下图:

    但是vue内部都具体怎么操作的一概不知,今天学习源码的过程中发现了终于知道了其中的奥秘。我们来一步步的解析这个过程都做了哪些事。先找到项目的入口文件。

     1.首先找到项目的入口文件

    我们会在启动vue项目的时候都会执行npm run dev,那这个指令都做了哪些操作,我们可以打开packge.json文件找到这行指令

     可以看到有个scripts/config.js文件,这就是这个指令执行的配置文件,我们打开config.js继续往下找

     这个是当前项目的入口地址。

    web/entry-runtime-with-compiler.js 运行时编译

     

     可以看到这个文件主要做的是扩展$mount方法,处理template和el选项,不管传过来的options是template还是el选项,最终都会编译成render函数

    同时导出了一个vue

     可以看到这个vue是从runtime/index文件引入的,打开这个文件

     这个文件定义了$mount方法,并且由mountComponent()这个方法执行挂载,将根组件挂载到宿主元素

    这个文件还不是真正定义vue的地方,这个文件导出的vue是从core/index文件引入的

     打开core/index文件

     这个文件定义了全局API,set、delete、nextTick等方法,同时从instance/index引入了Vue,打开这个文件

     终于找到了定义vue的地方,可以看到这个这个里面有几个核心方法及其作用,分别是

    initMixin(Vue)  // 实现init函数 
    stateMixin(Vue) // 状态相关api $data,$props,$set,$delete,$watch 
    eventsMixin(Vue)// 事件相关api $on,$once,$off,$emit 
    lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destroy 
    renderMixin(Vue)// 渲染api _render,$nextTick

    当执行new Vue的时候就是执行this._init()这个方法,进行生命周期的初始化,而init方法是由initMixin(Vue)这个方法实现的

    2.项目初始化init()方法的调用

     这个方法首先整合options选项,将用户传递的options选项与当前构造函数的options选项及其父级构造函数的options选项合并生成一个新的options并赋值给$options。

    然后再判断如果用户传递了el选项,就自动开启模板编译阶段与挂载阶段,如果没有el选项就需要用户手动执行vm.$mount方法进行模板编译和挂载阶段。其中ininState()会初始化事件、 props、 methods、 data、 computed 与 watch,我们着重来看一看data的初始化过程。其实就是数据响应化和依赖收集的过程。

    先判断data是否是函数,如果是函数的话就getData函数并将返回值赋给data和vm._data,daita中的数据会被保存在vm._data中。

    这个方法中有一个observe方法,对data中的属性进行遍历,执行相应的操作,具体看一下observe方法都做了什么

     这个方法返回了一个Observe实例

     判断当前数据类型

     如果是object类型就就执行defineReactive()方法

     

     

     defineReactive定义对象属性的getter/setter,getter负责添加依赖,setter负责通知更新 ,这是数据响应化的处理。再来看一下依赖收集,这个方法中有一个Dep,看一下它的作用

     Dep就是订阅者,负责管理一组Watcher(观察者),包括watcher实例的增删及通知更新 ,用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作; 用 notify 方法通知 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作 ,下面再看Watcher的作用

    watcher一创建就会执行get方法进行依赖收集

      

    Watcher解析一个表达式并收集依赖,当数值变化时触发回调函数,常用于$watch API和指令中。
    每个组件也会有对应的Watcher,数值变化会触发其update函数导致重新渲染

    到此,初始化过程就完成了。

    3. $mount执行挂载

    $mount方法是在runtime/index这个文件里面定义的

    我们可以看到$mount这个方法是由mountComponent()这个函数执行的

    可以看到,这个函数会new一个Watcher,调用更新函数更新组件

    其中updateComponent方法会做两件事,第一是通过_render()方法将之前编译的render function 渲染成VNode,第二是通过_updata()方法将虚拟dom转换成真实dom显示在页面上。

    接下来我们来分别看这两个步骤

    4.渲染函数render

    打开render.js

     这里面有个createElement()函数,它的核心作用是将用户传进来的render function转换成虚拟dom。

     

     其实render函数的执行结果就是createElement()的执行结果,在这里就不详细讲解其过程。

    接下来执行_updata()的方法,这个过程会先执行patch方法对新旧虚拟dom进行差异对比

    5.patch对比

     如果没有新的虚拟dom,表示是第一次初始化程序,就直接将这个虚拟dom作为真实dom,否则就表示要更新,这个时候需要新旧虚拟dom对比,由__patch__()这个方法实现

    打开vdom/patch.js,

    patch算法通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当 高效的算法。

    同层级只做三件事:增删改。具体规则是:new VNode不存在就删;old VNode不存在就增;都存在就 比较类型,类型不同直接替换、类型相同执行更新;

    两个VNode类型相同,就执行更新操作,包括三种类型操作:属性更新PROPS、文本更新TEXT、子节点更新REORDER patchVnode具体规则如下:
    1. 如果新旧VNode都是静态的,那么只需要替换elm以及componentInstance即可。

    2. 新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren

    3. 如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节 点加入子节点。

    4. 当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。

    5. 当新老节点都无子节点的时候,只是文本的替换。

    至此,vue的渲染过程执行完毕

    总结:new Vue() 之后的关键步骤

    1. 执行init方法进行初始化

       。进行初始化生命周期、事件、 props、 methods、 data、 computed 与 watch

       。initState()方法会进行data初始化,其实就是数据响应化 和 依赖收集 的过程

    在图中表现为这一部分

    2.编译

    如果是运行时编译,即不存在 render function 但是存在 template 的情况,需要调用第一个$mount() 方法进行「 编译」步骤,并最终生成render函数

    编译过程如下:

      。解析 - parse 

         解析器将模板解析为抽象语法树AST,只有将模板解析成AST后,才能基于它做优化或者生成代码字符串。

      。优化 - optimize 

         优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节 点。

      。代码生成 - generate 

        将AST转换成渲染函数中的内容,即代码字符串。 

    在图中表现为下面部分

     3、生成VNode,再将VNode转换成真实dom

     调用第二个$mount()方法,通过_render() 方法将 render funtion 生成虚拟dom,再通过_patch() 方法,进行diff算法,把虚拟dom转换成真实dom,显示在页面。在这个过程中会进行响应化处理,具体过程如下:

       。再将render function转换成VNode的过程中会读取data里面设置的属性,所以会触发getter方法,进行依赖收集,并将观察者 Watcher 对象存放到订阅者 Dep 的 subs 中如果数据发生变化就        会触发setter方法,通知更新视图,进行updata方法,这个方法会执行patch进行对比差异,然后更新。

    在图中表现为这部分:

    下面是真实开发调用栈的执行结果(当时截图软件和某一个热键冲突了截不了就直接拍照了)

          

     下面是整个代码执行思维导图

  • 相关阅读:
    [转]data类型的Url格式:把小数据直接嵌入到Url中
    SQL 找出某列最小的行记录.
    用 Dos 像数据库一样拎出所有文件.
    Js 正则表达式 RegExp .
    KMP算法C语言实现。弄了好久才搞好。。。
    python如何保证输入键入数字
    数据库关系图:“此数据库没有有效所有者,因此无法安装数据库关系图支持对象"的解决方法
    建立sql数组的一个函数
    实用的240多个jQuery插件
    begin tran,commit tran和rollback tran的用法
  • 原文地址:https://www.cnblogs.com/lyt0207/p/12289283.html
Copyright © 2011-2022 走看看