image

https://www.processon.com/vie...

https://www.processon.com/vie...

目标

  1. 掌握源码学习方法
  2. vue初始化原理解剖
  3. 深入理解数据响应式

配置环境

  1. 拷贝代码: git clone https://github.com/vuejs/vue.git
  2. npm install
  3. 安装打包工具rollup npm i -g rollup
  4. 修改package.json 中dev的打包配置"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
  5. 执行脚本 npm run dev
  6. 在examples文件夹中添加test文件

文件目录

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 # 平台独特的代码 代码扩充

vue源码源码分析-初始化流程

入口

dev脚本中 scripts/config.js 配置文件
web-full-dev 表示执行时的配置项

'web-full-dev': {
    // 入口
    entry: resolve('web/entry-runtime-with-compiler.js'),
    // 出口
    dest: resolve('dist/vue.js'),
    // 格式
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
入口文件

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

首先是扩展$mount方法, 处理template或者是el选项

// 扩展$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean): Component {el = el && query(el)
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
  // 用户配置的选项
  const options = this.$options
  // 首先判断render是否存在
  if (!options.render) {
    // 获取template选项
    let template = options.template
    // 判断template是否存在 如果template不存在判断el是否存在
    if (template) {
      
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      // 获取到html模板字符之后  执行编译过程
    }
  }
  // 执行挂载
  return mount.call(this, el, hydrating)
}
Vue.prototype.$mount的来源

路径: src/platforms/web/runtime/index.js

// 实现了一个patch函数 主要用于初始化和更新
Vue.prototype.__patch__ = inBrowser ? patch : noop
// 实现了$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 定义$mount 挂载组件, 将vnode转化为node
  return mountComponent(this, el, hydrating)
}
查找vue

路径: src/core/index.js

// 初始化全局api 比如component/ fitter/ directive/ use/ mixin/ util/ extend
initGlobalAPI(Vue)
查找vue引入

路径: src/core/instance/index.js
声明了Vue构造函数

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 初始化方法
  this._init(options)
}

// 实例属性  实例方法
initMixin(Vue)  // 混入了_init()方法
stateMixin(Vue) // $data/$props/ $set() $delete()  $watch()
eventsMixin(Vue)  // $emit() / $on() $off()  $once()  事件相关方法
lifecycleMixin(Vue)  // _update / $forceUpdate/ $destory  生命周期相关方法
renderMixin(Vue) // $nextTick()/ _render() // 渲染相关

initMixin方法
路径: src/core/instance/init.js

// 实现一个_init方法 _init的目的是供内部使用
Vue.prototype._init = function (options?: Object) {
    
    //做选项合并 将传入的选项和vue的默认选项进行合并
    if (options && options._isComponent) {
      
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // 初始化核心代码
    vm._self = vm
    initLifecycle(vm)  // $parent $root  $children  $refs
    initEvents(vm)  // 自定义事件监听
    initRender(vm) // $slots/ $createElement  定义$attrs 和$listeners响应式
    // 上面方法在beforeCreate之前, 进行初始准备
    callHook(vm, 'beforeCreate')  // 派发beforeCreate, 在beforeCreate中可以访问上面三个内容
    initInjections(vm) // resolve injections before data/props
    initState(vm)  // props/ methods/ data/ computed /watch 数据初始化
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    // 当设置了el选项时, 自动调用$mount
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
整体流程:

new Vue() => 创建vue实例
_init() => 初始化数据, 属性, 事件
$mount() => 执行挂载, 将虚拟dom转化成真实dom

mountComponent()=>    将虚拟dom转化成真实dom
new Watcher() => 创建组件渲染watcher
updateComponent() => 执行初始化或更新
_update() => 初始化或更新, 将虚拟dom转化成真实dom
_render()  渲染组件,获取vdom~~~~

Vue源码分析 数据响应式

vue2实现双向绑定,使用了js对象的Object.defineProperty()对data的setter/getter方法进行拦截, 结合发布订阅模式,在getter中进行订阅.在setter中进行发布通知,让所有订阅者完成响应
具体实现是在初始化Vue时, 会调用initState(vm)方法

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数据响应式处理

路径: 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
//做递归处理 
  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)  
    }  
}

lolo
12 声望0 粉丝