学习目标

vue源码分析(一)
  1. 掌握源码学习方法
  2. 初始化流程梳理
  3. 深入理解数据响应式过程

配置环境

  1. 首先拷贝源码: git clon https://github.com/vuejs/vue.git
  2. cd ./vue
  3. npm i
  4. 安装 rollup:打包工具
  5. 需要全局安装 npm i -g rollup
  6. 修改 /yuanma-vue/vue/package.json 中dev的脚本: 添加 --sourcemap
  7. 执行dev脚本:npm run dev
  8. 在 examples 中去添加 test文件

注:打完断点可以进行测试,源码运行流程

源码环境

  "name": "vue",  "version": "2.6.11",

文件目录

vue├──dist #发布目录├──examples #范例,测试代码在这里├──flow #试代码├──packages #核心代码之外的独立库├──scripts #构建脚本├──src #源码├──test #ts类型声明,上面flow└──types #对flow的类型声明 

重点:源码

  src # 源码    ├──compiler  # 编辑器相关  ├──core  # 核心代码  ├──────components # 通用组建如 keep-alive  ├──────global-api # 全局API  ├──────instance # 构建函数等  ├──────observer # 响应式相关  ├──────vdom # 虚拟DOM相关  └──────platforms # 平台独特的代码 代码扩充

dist中不同命名输出的解释

common:nodejs打包的语言输出,服务端渲染。 vue.common.dev.js esm:版本是webpack打包用的版本 es6的模块umd:universal module definition(通用模块定义)  兼容common 和amd 用于浏览器;前端浏览器 加runtime含义的不同 添加指不包含编译器:体较小;仅包含运行时,(就不可以用字符串模版) 运行时必须经过webpack,因为webpack在程序运行之前,提前打包编译,所以会将template,<p>{{foo}}</p>等等都预编译成渲染函数, 在webpack情况下,就不需要编译器不添加指包含编译器比如: 当我们在 new Vue({  template:'<div></div>' // 这种格式就需要 需要添加编译器 字符串模版})<!-- <script src="../../dist/vue.js"></script> -->// 什么没有没有加的,umd格式 vue.js

vue源码分析-初始化流程

入口文件:

路径:src/platforms/web/entry-runtime-with-compiler.js

// 首先获取到定义的$mount 方法,并赋值给mount;
//主要目的是要重写$mount方法;还需要执行之前的mount方法;
// 所以主要是扩展$mount方法

const mount = Vue.prototype.$mount  Vue.prototype.$mount = function ( el?: string |Element, hydrating?: boolean): Component {      **1.用户传进来的el 宿主元素**      **2.处理选项:render/template/el**      const options = this.$options      if(!options.render){......}else if(el){.....} //**都是在处理选项  **    从这里可以看出来,在vue处理render/template/el的时候的优先级。      //首选判断render是否存在    if (!options.render) { //当render不存在的时候,才会进到里面的选项。      // 如果 template存在,再走template的if选项,紧跟着是else if          let template = options.template          if (template) {          运行编译:将模板为render函数          }else if (el) {}          从而可以得知 优先级为: render> template > el        }      }      扩展$mount的主要目的是:判断是否需要编译出渲染函数      在组件开发的情况下,会有一个main.js      main.js      new vue({          render(h){              return h(App) //编译根组建App              }      }).$mount('#app')    render 需要配合$mount 来使用}
Vue.prototype.$mount的来源

路径:src/platforms/web/entry-runtime-with-compiler.js中
import Vue from './runtime/index'
路径src/platforms/web/runtime/index.js文件中,目录还是在web平台

web平台运行时的首页需要实现的功能有哪些:  **1\. 安装平台patch函数,主要是用于初始化和更新两个功能(虚拟dom、diff)**  // install platform patch function  Vue.prototype.\_\_patch\_\_ = inBrowser ? patch : noop  **2.实现$mount 函数**  Vue.prototype.$mount = function (...){      el:宿主      **$mount唯一做的一件事:挂载组建 就是将vnode转化为node 可以理解为vdome 转化为dome**      return mountComponent(this, el, hydrating)  }

查找vue来源

初始化全局的API

在src/platforms/web/runtime/index.js文件中,

import Vue from 'core/index'  Vue.prototype.$mount = function (){}  路径:src/core/index.js  1.初始化全局的APIinitGlobalAPI(Vue)  如: set delete nextTick ....等等  具体的定义可以进到initGlobalAPI 里来看,举例子:  在import { initUse } from './use'中进入 initUse  可以看到Vue.use方法  Vue.use方法,之前疑惑为什么要在,plugin去定义install方法,从源码就可以看到约定的installif (typeof plugin.install === 'function') {.....}

在src/core/index.js中
import Vue from './instance/index'
initGlobalAPI(Vue)

查找vue的引入

路径:/src/core/instance/index.js
这才是Vue实例,构造函数声明的位置
在这,构造函数

    function Vue (options) {......      // 在这里面做了一件事,是初始化方法      this.\_init(options)      }      //实现实例方法和属性      initMixin(Vue) // 这里做了一次混入,init方法的声明 \_init()      stateMixin(Vue) // 和状态相关的实例方法 $set/$delete/$watch /.....      eventsMixin(Vue) // 和事件相关的方法 $on/$off/$once/$emit      lifecycleMixin(Vue) // 和生命周期相关的方法      renderMixin(Vue) // 渲染相关:声明和实现 $nextTick /\_render    
initMixin(Vue) 声明_init

路径: src/core/instance/init.js

 export function initMixin (Vue(将vue进行传入): Class<Component>) {.....      // \_init 做一个实例方法: \_init用下划线的目的是,内部使用      Vue.prototype.\_init = function (options(传进来的选项)?: Object) {.....         初始化的核心代码在这里        //选项的合并:vue会有默认选项,和用户传进来的选项,进行合并,比如用户传进来的el         if (options && options.\_isComponent) {.....}            else {.....}      }  }
stateMixin(Vue) // 和状态相关的实例方法**

路径:src/core/instance/state.js

 export function stateMixin (Vue: Class<Component>){.....  //$set   Vue.prototype.$set = set  //$del   Vue.prototype.$delete = del  //$watch  Vue.prototype.$watch = function (....){....}  }
lifecycleMixin(Vue) //和生命周期相关的方法

路径:src/core/instance/lifecycle.js

 export function lifecycleMixin (Vue: Class<Component>) {......         //定义例如\_update:更新 将虚拟dome 转化为真实dome;       Vue.prototype.\_update = function (vnode: VNode, hydrating?: boolean) {}         .......         //强制更行         Vue.prototype.**$forceUpdate**\= function () {            const vm: Component = this            if (vm.\_watcher) {             // 主要就是将所有的watcher 更新一遍              vm.\_watcher.update()            }         }          // 实例销毁          Vue.prototype.**$destroy**\= function () {}             .....等等  }
initMixin(Vue) 声明_init 里面完成的事情
nitLifecycle(vm) // $parent $root $children  initEvents(vm) // 事件监听initRender(vm) // vm.$slots // vm.$createElement// 在beforeCreate 之前,上面的是一个初始准备  声明周期调用的统一方法是callHook,这个方法  callHook(vm, 'beforeCreate') //去派发注册事件,所以在beforeCreate中,是可以访问到上面三个内容的  initInjections(vm) // resolve injections before data/props   initState(vm) //核心:数据初始化  // 为什么 Injections 会在Provide 之前去注册?因为继承问题,Injec可以拿到之前的祖辈的,之后在进行分发下去,这样,Provide就可以拿到祖辈传过来了,以及在state里面注册的数据 initProvide(vm) // resolve provide after data/props  callHook(vm, 'created') // 所有数据相关的,都在beforCreate和created之间,上面三个内容,才会将数据变成响应式。
initLifecycle(vm)

路径:src/core/instance/lifecycle.js

export function initLifecycle (vm: Component) {.....  初始化的时候,只有根组建和父组建}
initEvents(vm)

路径:src/core/instance/events.js

export function initEvents (vm: Component(当前组建)) {      vm.\_events = Object.create(null)      vm.\_hasHookEvent = false      // init parent attached events      //事件处理- 谁派发,谁监听;    // vm.$options.\_parentListeners:从选项中拿到,在parent(父级)中去声明的事件监听器      const listeners = vm.$options.\_parentListeners //初始化父组件添加的事件      if (listeners) { // 如果父组建注册是事件存在,监听器存在          // 子组建就去注册事件          updateComponentListeners(vm, listeners)      }  }
initRender(vm)

路径:src/core/instance/render.js

vm.$slotsvm.$createElement// 在new Vue中的“h”

vue源码分析-数据响应式

数据响应式是MVVM的特点,vue数据双向绑定的原理:是利用了JS语 言特性Object.defineProperty(),通过定义对象属性setter方法拦截对象属性变更,从而将数值的变化 转换为UI的变化。
具体实现是在Vue初始化时,会调用initState,它会初始化data,props等
initState(vm)

路径: src/core/instance/state.js

export function initState (vm: Component) {.....      // 判断data是否存在      if (opts.data) { //如果data存在,走这里,因为要看数据的双向绑定,所以找到initData          initData(vm)       } else {          observe(vm.\_data = {}, true /\* asRootData \*/)       }  }
当前目录下-initData(vm)
function initData (vm: Component) {....      // 传进来的data可能是函数,或者对象,目的是防止数据污染;      let data = vm.$options.data     data = vm.\_data = typeof data === 'function'     ? getData(data, vm)     : data || {}    ....     //代理、重复判断   const keys = Object.keys(data)     while (i--) {....        //属性和方法不能是重复的判断      if (process.env.NODE\_ENV !== 'production') {}        if (props && hasOwn(props, key)) {}      }      // 对data数据响应式处理      observe(data, true /\* asRootData \*/)  }
对data数据响应式处理

observe(data, true /* asRootData */)
路径:src/core/observer/index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {....      // 为传进来的对象value,创建一个observer实例;任何一个对象都伴随一个observer实例      // 返回一个observer实例      // 用observer实例,来判断类型 以及内部响应式处理        // 如果做过响应式,那么\_ob\_ 存在,就直接返回      if (hasOwn(value, '\_\_ob\_\_') && value.\_\_ob\_\_ instanceof Observer) {          ob = value.\_\_ob\_\_       } else if (...){...}else{...         // 如果未处理,就创建一个新的实例 ob实例         ob = new Observer(value)       }  }
new Observer(value)

路径:src/core/observer/index.js

export class Observer {      constructor (value: any) {         // 可以理解为小管家:比如在data中的数据是对象包对象的存在, data:{obj:{foo:foo}},对对象来说,是对对象新             增和删除的通知工作;由小管家来通知的。         // 当需要修改obj里面的属性,是在肚子里发生的事情,所以需要小管家来进行处理         // 如果obj是一个数组,选项的删除也是需要小管家         // 对象属性变化或者数组元素变化需要小管家通知更新         this.dep = new Dep() //小管家     //判断传入的value类型,做相应的处理       if (Array.isArray(value)) { //当是Array时的处理               //覆盖数组实例的原型          }else{              //对象的处理            this.walk(value)         }      }      walk (obj: Object) {....           const keys = Object.keys(obj)           //遍历当前的所有keys           for (let i = 0; i < keys.length; i++) {              //对每个key进行拦截              defineReactive(obj, keys\[i\])           }      }  }
defineReactive
export function defineReactive (     obj: Object,    key: string,    val: any,    customSetter?: ?Function,    shallow?: boolean  ) {......        // 大管家 和key 一对一的关系      const dep = new Dep()     // 用户自定义一些属性     const property = Object.getOwnPropertyDescriptor(obj, key)     ..............      //核心: 递归,如果val是对象,则获取一个子oberser实例,一个对象就会有一个observer实例      let childOb = !shallow && observe(val)    //做递归处理    Object.defineProperty(obj, key, {        enumerable: true,        configurable: true,        get: function reactiveGetter () {            // 依赖收集            const value = getter ? getter.call(obj) : val            // Dep.target 可以理解为watcher的实例,每次触发watcher实例的时候,会手动触发,get事件一下获取当前                 值,将实例放在Dep.target上;            if (Dep.target) {                 // dep和watcher是n对n的关系,除了页面渲染{{name}}会触发watcher以外;还有可能是computed 或者w                    atch触发的事件监听;                 // 双向添加两者关系                  dep.depend()                 // 若存在子ob                 if (childOb) {                 // 把当前的watcher和子ob中的dep建立关系                 childOb.dep.depend()                 if (Array.isArray(value)) {                    dependArray(value)                 }             }          }          return value        },        set: function reactiveSetter (newVal) {}        }  }
dep.depend

路径:src/core/observer/dep.js

depend () {      if (Dep.target) {      //执行的是当前watcher的addDep     Dep.target.addDep(this)      }  }
Dep.target.addDep

找到watcher的addDep事件
路径:src/core/observer/watcher.js

addDep (dep: Dep) { //dep和wacter相互保存的关系      const id = dep.id      if (!this.newDepIds.has(id)) { // 是否和当前的dep建立关系          // 没有就创建dep和watcher的关系          this.newDepIds.add(id)          this.newDeps.push(dep)          if (!this.depIds.has(id)) {              // 创建dep和watcher              dep.addSub(this)          }      }  } 
dep.addSub

src/core/observer/dep.js

export default class Dep {      //往自己的sub 里面去push      addSub (sub: Watcher) {          this.subs.push(sub)      }  }

学习资料:

思维导图vue源码解析(一): https://www.processon.com/vie...
获取vue源码项目地址:https://github.com/vuejs/vue
迁出项目: git clone https://github.com/vuejs/vue.git
当前版本号:2.6.11

我写的主要是梳理,初始化流程和响应式过程的一个学习思路。如果有错误,欢迎补充。如果觉得在这里看文档不太清晰,可以去我写的思维导图的地址去看下。
接下来还会整理出源码的其他内容,尽情期待~~~拜拜