Javascript

vue2源码解析(一)

本文主要是介绍vue2源码解析(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

src\platforms\web\entry-runtime-with-compiler.js 源码开始位置(引入了Vue构造函数)

扩展$mount,处理可能存在的templete或者el选项,重新编译template为render函数

src\platforms\web\runtime\index.js(按照上面的引入vue往上查找)

一、定义了一个_patch_函数 

render函数的目的:获取虚拟dom vdom

patch函数的目的:(diff算法也在里面)1、初始化 2、更新

不管是初始化还是更新,都是将虚拟dom变成真实dom

$mount的目的:生成真实dom($mount里面一定会调用render和patch)

二、实现$mount,这样在第一个路径里才可以扩展mount

$mount最后return了一个mountComponent(vm,el,  hydrating:boolean)

src\core\index.js(再往上查找Vue构造函数)

initGlobalAPI(Vue) 初始化全局api   例如:Vue.component/filter/directive/use/mixin/util/extend   src\core\instance\index.js(继续往上查找到Vue构造函数) 1、声明构造函数
// 1.声明构造函数
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)执行this._init方法
}

2、实例属性,实例方法

// 2.实例属性,实例方法:
initMixin(Vue) // _init()
stateMixin(Vue) // $data/$props/$set()/$delete()/$watch()
eventsMixin(Vue) // $emit()/$on/$off()/$once()
lifecycleMixin(Vue) // _update()/$forceUpdate()/$destroy()
renderMixin(Vue) // $nextTick()/_render() 

  进入initMixin()方法当中(实现_init初始化方法)

问题:new Vue()的时候都发生了什么?

实现_init初始化方法,都做了哪些事情

1、合并选项,代理$data(merge options)(本来有自己写的data,el等,再合并vue提供的一些初始化options,例如filters,component等)

2、初始化核心逻辑

initLifecycle(vm) // $parent/$root/...初始化生命周期
initEvents(vm) // 自定义事件监听(事件的派发和监听都是同个组件)
initRender(vm) // $slots 插槽的初始化/$createElement=》 render函数中的h就是createElement/定义$attrs和$listener的响应式(
  defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
)
callHook(vm, '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')

3、当设置了el选项时,自动调用了$mount

// 当设置了el选项时,自动调用$mount
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }

$mount把虚拟dom变成真实dom,查看Vue.prototype.$mount,它真正调用的是mountComponent()函数

接下来是mountComponent()函数

1、先是callHook(vm,beforeMount),即在挂载之前先执行beforeMount

2、后面new Watcher(vm,updateComponent(更新函数)) 这说明了一个组件就会有一个watcher 

updateComponent  = ()=> {  vm._update(vm._render(),hydrating)}

所以执行顺序如下:

$mount    =>     _render()渲染函数,获取当前组件的虚拟dom  vnode   =>  _update() 将虚拟dom转化为真实dom

下面的截图是_update() 的具体处理逻辑

 

 

 总结(以上的整体流程捋一捋)

new Vue()==>   _init   ==>   $mount  ==>  mountComponent   ==> new Watcher()  ==> updateComponent   ==> _render()   ==>  _update()   ==> __patch__ 是否是初始化渲染,是的话将虚拟dom转化为真实dom,不是的话patch走diff算法对比

数据响应式

 起始位置:new Vue()  =>  initMixin()  =>   初始化核心逻辑中 initState() 主要处理props/methods/data/computed/watch的初始化  =>  接下来主要看initState中对data的响应式处理

递归响应式处理会触发observe()函数,每个函数都会只要有一个对象,就会产生一个ObServer的实例:

每个对象一个Ob实例,作用是判断对象类型做相对应的响应式处理(数组和对象)

如果这个对象已经是一个响应式的数据,就会有一个标识,这个标识是“__ob__”,所以我们经常在控制台看到的“__ob__”,说明这个对象已经是一个响应式的对象

接下来看Observer构造函数

Observer构造函数判断是数组(Array.isArray())还是对象(this.walk(value))  walk中遍历所有data属性,执行defineReactive()

注意:Observer中也有dep(大管家:负责对象如果有动态新增或删除属性时通知更新。数组有新元素增加和删除,通知更新)

进入defineReactive()函数,作用:给一个对象定义一个响应式属性

const  dep = new Dep (小管家:dep和data中的所有key都是一对一的关系)(如果key的值发生变化,通知更新)

defineReactive函数
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend() // dep和watcher互相添加映射关系(dep和watcher是多对多的关系)
        // 子Ob实例也要添加映射关系
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })

 响应式处理数组的源码

// 1.获取原型
const arrayProto = Array.prototype
// 2.克隆副本
export const arrayMethods = Object.create(arrayProto)
// 3.定义要覆盖的7个方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
// 4.遍历覆盖
methodsToPatch.forEach(function (method) {
  // cache original method
  // 5.获取原始方法
  const original = arrayProto[method]
  // 6.覆盖该方法
  def(arrayMethods, method, function mutator (...args) {
    // 7.先执行原始方法
    const result = original.apply(this, args)
    // 8.扩展逻辑:变更通知
    const ob = this.__ob__
    // 如果是插入型操作,对新插入的元素要做响应式处理
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 变更通知
    ob.dep.notify()
    return result
  })
})

  

 

这篇关于vue2源码解析(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!