Vue源码学习

171 阅读3分钟

准备工作

认识Flow

  • flow是Facebook出品的JavaScript静态类型检查工具
  • 类型检查是当前动态类型语言的发展趋势
  • 工作方式
    • 类型推断:通过上下文推断类型
    • 类型注释:事先注释期待的类型;以:开头,可以在函数参数、返回值和变量声明中使用
  • .flowconfig为配置文件

目录设计

  • compiler:编译相关,将模板ast语法树的解析、优化、代码生成等
  • core:vue核心代码,包括内置组件、全局API封装、Vue实例化、观察者、虚拟dom、工具函数等
  • platform:vuejs不同平台的入口
  • server:服务端使用的vue先关
  • sfc:将.vue解析为js对象
  • shared:共用的工具方法

源码构建

  • 基于rollup构建
    • entry:构建入口文件地址
    • dest:构建后文件地址
    • format:构建的个税
      • cjs:commonJS规范
      • es:ES Module规范
      • umd:UMD规范
  • Runtime Only:运行时不做编译,编译提前完成
  • Runtime Compiler:运行时编译
  • 构建过程:
    • step1:package.json -> node scripts/build
    • step2:scripts/build
      • step2.1:从./config中获取所有配置
      • step2.2:根据脚本参数进行过滤
      • step2.3:递归进行build

引入vue过程

  • 过程:
    • step1:src/platforms/web/entry-runtime-with-compiler.js:重新定义$mount方法,将template转为render函数,并调用原有的$mount方法
    • step2:src/platforms/web/runtime/index.js:定义$mount方法,扩展config.option等属性
    • step3:src/core/index.js:定义vue原型上属性,以及一些静态方法(initGlobalAPI)
    • step4:src/core/instance/index.js:定义Vue函数,并混入原型方法
  • 优点:Function实现类,按功能分散到多个模块中去,利于代码维护和管理

数据驱动

数据驱动:指视图是由数据驱动生成的,我们对视图的修改不会直接操作dom,而是修改数据

new Vue发生了什么

  • 检测是否为new调用
  • 调用_init方法
    • 将this赋值给vm
    • 定义vm.uid
    • 定义vm._isVue
    • 定义vm.$option
      • 是组件:调用initInternalComponent
      • 否则:合并传入的options
    • 定义定义vm._renderProxy(生产和开发环境)
    • 定义vm._self为vm
    • 初始化生命周期
    • 初始事件中心
    • 初始化渲染
    • 调用生命周期钩子beforeCreate
    • 初始化注入
    • 初始化state(prop,method,data等)
    • 初始化provider
    • 调用生命周期钩子created
    • 调用vm.$mount【】

Vue实例挂载的实现

  • 调用vm.$mount
    • 将el转为dom(并判断el为body或html时,退出函数)
    • 创建render函数,挂载到vm.$options.render上
      • 转换tempalte
        • 可能是自定义
        • 可能是id所代表的dom
        • 可能是el
      • 调用compileToFunctions定义option的render和staticRenderFns
    • 调用mount【】
  • 调用mount
    • 调用mountComponent【】
  • 调用mountComponent
    • 将el赋值给vm.$el
    • 调用生命周期钩子beforeMount
    • 定义updateComponent方法
    • 创建一个渲染watcher【】,创建时会调用一次updateComponent【】
  • 调用updateComponent
    • 先调用vm._render生成vnode【】
    • 再调用vm._update将上一步生成的vnode转成dom并挂载【】

render

  • 调用render(vm.mount调用时生成的,其内部会调用vm.createElement)

Virtual DOM

  • 用原生js对象去描述dom节点,只用来映射到真实dom渲染,不包含dom操作方法
  • 从vnode到dom包含create、diff、patch等过程

createElement

  • 调用vm.$createElement
    • 对参数进行调整
    • 调用_createElement【】
  • 调用_createElement
    • 当data为响应式数据是,返回empty vnode
    • 当tag不存在时,返回empty vnode
    • 当tag为string时:
      • 将children先转换为children的vnode
      • 再根据tag、children等生成自身vnode
    • 当tag不为string时:调用createComponent
    • 再根据生成vnode的情况,做一下过滤

update

vm._update的调用时机有两个一个是首次渲染,一个是数据更新

  • 调用vm.__ patch__createPatchFunction(nodeOps,modules)生成的,nodeOps:dom相关的操作,modules:dom属性的
    • oldVnode
      • 未定义
        • ...
      • 定义
        • 非真实dom且oldVnode与vnode相同
          • ...
        • 其他
          • 调用emptyNodeAt,创建了一个空的和el相同标签元素的vnode,并且将dom挂在elm上了
          • 调用createElm,完成dom的生成
            • 先尝试创建组件
            • 再根据vnode,创建chidren(在创建chidren时,会递归调用createElm)
            • 再将其挂载到调用emptyNodeAt生成的dom上
          • 对vnode.parent进行相关操作
          • 删除父级dom
    • 调用invokeInsertHook

组件化

createComponent

当render传参为一个component时,调用createComponent(定义在src/core/vdom/create-component中)方式生成vnode

  • 调用Vue.extend【】方法(定义在src/core/global-api/extend.js中),重新定义组件的构造函数
  • 定义组件周期钩子:installComponentHooks
  • 调用VNode构造函数创建vnode(children定义为undefined且定义了componentOptions)