美文网首页
Vue vue2源码解析

Vue vue2源码解析

作者: 爱吃馒头不吃辣 | 来源:发表于2021-01-09 14:51 被阅读0次

    入口文件

    vue-2.6.11\src\platforms\web\entry-runtime-with-compiler.js

    1. 从vue原型中拿出$mount进行覆盖,

      // 17行
      const mount = Vue.prototype.$mount
      
    2. 解析模板相关选项

      // 18行
      Vue.prototype.$mount = function (
        el?: string | Element,// 宿主元素
        hydrating?: boolean
      ): Component {
        //获取真实DOM
        el = el && query(el)
      // 34行
      // 先判断有没有render,render函数优先级最高
      // 优先级:render > template > el
      if (!options.render) {
          let template = options.template
          // 再判断有没有template并解析
          if (template) {
            if (typeof template === 'string') {
              if (template.charAt(0) === '#') {
                template = idToTemplate(template)
                /* istanbul ignore if */
                if (process.env.NODE_ENV !== 'production' && !template) {
                }
              }
            } else if (template.nodeType) {
              template = template.innerHTML
            } else {
              return this
            }
          // 最后查看el选项
          } else if (el) {
            template = getOuterHTML(el)
          }
          // 处理模板的方式,编译它,最终目标是render
          if (template) {
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
              mark('compile')
            }
      
            const { render, staticRenderFns } = compileToFunctions(template, {
              outputSourceRange: process.env.NODE_ENV !== 'production',
              shouldDecodeNewlines,
              shouldDecodeNewlinesForHref,
              delimiters: options.delimiters,
              comments: options.comments
            }, this)
            // 重新复制给选项
            options.render = render
            options.staticRenderFns = staticRenderFns
      
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
              mark('compile end')
              measure(`vue ${this._name} compile`, 'compile', 'compile end')
            }
          }
        }
      

    初始化挂载

    vue-2.6.11\src\platforms\web\runtime\index.js

    1. 安装平台特有的补丁(patch)函数:做初始化和更新,为了实现跨平台,平台特有的操作。

      // 33行
      Vue.prototype.__patch__ = inBrowser ? patch : noop
      
    2. 实现$mount:初始化挂载。

      1. $mount('#app')=>mountComponent:render()=>vdom=>patch()=>dom=>appendChile()
      2. 初始化时执行$mount('#app') ,在内部调用mountComponent,调用内部的render函数,核心目标把当前虚拟DOM树传给patch()函数变成真实DOM,真实DOM挂载到#app上。
      // 36行
      Vue.prototype.$mount = function (
        el?: string | Element,
        hydrating?: boolean
      ): Component {
        el = el && inBrowser ? query(el) : undefined
        return mountComponent(this, el, hydrating)
      }
      

    初始化全局API

    vue-2.6.11\src\core\index.js

    1. 初始化全局API:Vue.use,component,directive,filter,mixin,set,extend,delete

      // 6行
      initGlobalAPI(Vue)
      

    Vue的构造函数

    vue-2.6.11\src\core\instance\index.js

    1. 构造函数:new Vue(options)

      // 8行
      function Vue (options) {
        // 初始化
        this._init(options)
      }
      
    2. 声明实例属性和方法,把构造函数传递进去,通过混入模式把原型加上_init方法。

      initMixin(Vue)
      // 熟悉的其他梳理属性和方法,有下面这些混入
      stateMixin(Vue)
      eventsMixin(Vue)
      lifecycleMixin(Vue)
      renderMixin(Vue)
      

    初始化

    vue-2.6.11\src\core\instance\init.js

    1. 原型挂载_init实现原型方法,在任何实例都可使用vm._init

      Vue.prototype._init = function (options?: Object)
      
    2. 合并选项

      // 30行
      // 合并选项:new Vue传入的时用户配置,需要和系统配置合并
      if (options && options._isComponent) {
        initInternalComponent(vm, options)
      } else {
        vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
         )
      }
      
    3. 初始化操作

      // 52行
      // 生命周期初始化 组件实例相关的属性初始化,$parent,$root,$children,$refs
      initLifecycle(vm)
      // 事件初始化 监听自定义事件
      initEvents(vm)
      // 解析插槽,$slots,$scopeSlots,$createElement()
      initRender(vm)
      callHook(vm, 'beforeCreate')// beforeCreate生命周期
      
      // 组件状态相关的数据操作
      // inject/provide 注入祖辈传递下来的数据。
      initInjections(vm) // 
      // 把自己内部的状态进行响应式的处理,`props`,`methods`,`data`,`computed`,`watch`
      initState(vm)
      // 提供给后代,用来隔代传递参数
      initProvide(vm) 
      callHook(vm, 'created')// created生命周期
      
    4. initEvents(vm)

      1. 为什么要从父组件找Listeners?
        1. 子组件上有事件选项,但是在父组件内声明的。
        2. 派发和监听者都是子组件本身(this.$on(),this.$emit());
        3. 事件的真正监听者时子组件自己。
        4. updateComponentListeners从父组件身上拿出事件给子组件。
      export function initEvents (vm: Component) {
        vm._events = Object.create(null)
        vm._hasHookEvent = false
        // 找到父组件的选项
        const listeners = vm.$options._parentListeners
        if (listeners) {
          updateComponentListeners(vm, listeners)
        }
      }
      
    5. initRender(vm)

      // 34行
      // 这里的$createElement就是render(h)
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
      
    6. 如果设置了el,自动执行$mount()

      if (vm.$options.el) {
         vm.$mount(vm.$options.el)
      }
      

    Vue执行流程

    1. 进入src\core\instance\index.js文件内的Vue函数,执行this._init(options)

    2. 再进入src\core\instance\init.js文件内的initMixin函数。

      1. 32行此时的options还是用户设置的初始化选项,datael等。

      2. 先判断是否为isComponent,若不是执行mergeOptions将用户设置和系统设置进行合并。

        1. 合并完成后vm.$options是合并后的结果,可使用全局组件等{components{KeepAlive,Transition,TransitionGroup},data,el,filters}
      3. 执行initProxy进行代理设置。

      4. 依次执行初始化操作,

        1. initLifecycle(vm),vm新增属性$children,$parent,$refs但是值为空,$rootVue根实例。

        2. initEvents(vm),vm新增属性_events事件监听

        3. callHook(vm, 'beforeCreate')派发生命周期,所以此时不可以操作数据。

        4. initState(vm),数据的代理和响应式发生在这里,vm新增数据相应式,新增_data(响应式有_ob_),$data(非响应式)

          1. 进入文件src\core\instance\state.js,执行initState函数,如果有重名按处理顺序,谁先占用谁使用。

            1. if (opts.props) initProps(vm, opts.props)处理props

            2. if (opts.methods) initMethods(vm, opts.methods)处理methods

            3. if (opts.data) { initData(vm) } else {observe(vm._data = {}, true) }处理data

              1. 如果有data执行initData(vm)
                1. 执行initData函数,判断是函数还是对象再做相应处理。
                2. 校验命名冲突,根据propsmethods
                3. 最后执行observe(data, true)递归响应式处理。
              2. 如果没有data执行observe(vm._data = {}, true)
                1. 进入文件core\observer\index.js
                2. 获取Ob实例,是否作为根数据。
                  1. 如果有_ob_直接返回,ob = value.__ob__
                  2. 如果没有则创建,ob = new Observer(value)
                    1. 初始化时先给data的值创建一个ob,结果对象就有几个ob
                    2. 创建dep实例this.dep = new Dep():对象也需要dep,对象如果动态增减属性。
                    3. Observer区分对象还是数组。
                    4. 判断类型,指定ob实例。
                      1. 如果时数组执行数组响应式this.observeArray(value)
                        1. 进入文件src\core\observer\array.js
                        2. 默认的7个方法不会通知更新,将原有7个方法拦截修改。
                        3. 获取数组原型,const arrayProto = Array.prototype
                        4. 克隆一份新原型,export const arrayMethods = Object.create(arrayProto)
                        5. 7个变更方法需要覆盖,const methodsToPatch = [ 'push', 'pop', 'shift','unshift', 'splice', 'sort', 'reverse']。因为这7个会改变数组,其他的返回新数组。
                        6. 遍历7个方法,每次拿出一个方法保存原始方法const original = arrayProto[method]开始覆盖。
                        7. 执行默认方法const result = original.apply(this, args)
                        8. 对新加入的元素进行响应式if (inserted) ob.observeArray(inserted)
                        9. 变更通知const ob = this.__ob__
                        10. ob内部有dep,让dep通知更新ob.dep.notify()
                        2. 如果是对象执行对象响应式this.walk(value),执行defineReactive(obj, keys[i])
                        1. 每个key对应一个depconst dep = new Dep()
                        2. 依赖收集dep.depend(),vue2中一个组件是一个Watcher
                        1. dep:n=>wtacher:1,多对一。
                        2. 通过diff算法比对变化。
                        3. 进入文件src\core\observer\dep.js
                        1. depend函数内执行的Dep.target.addDep(this)watcher.addDep()
                        2. 进入文件src\core\observer\watcher.jsaddDep函数。
                        1. 相互添加引用的过程。
                        2. watcher添加depthis.newDepIds.add(id)this.newDeps.push(dep)
                        3. dep添加watcherdep.addSub(this)
                        3. 用户手动创建Watcherthis.$watch(key,cb)
                        1. dep:n=>wtacher:n,多对多。
                        4. 子ob也要做依赖收集工作childOb.dep.depend()
            4. if (opts.computed) initComputed(vm, opts.computed)处理computed

            5. if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }处理watch

      5. 执行挂载过程vm.$mount(vm.$options.el)

    3. 进入文件src\platforms\web\runtime\index.js执行Vue.prototype.$mount挂载函数。

      1. 执行mountComponent(this, el, hydrating)将虚拟DOM转换成真实DOM。
    4. 进入文件src\core\instance\lifecycle.js

      1. 执行mountComponent函数内部,挂载callHook(vm, 'beforeMount')生命周期钩子。
      2. 声明updateComponent组件更新函数函数未执行。
        1. vm._render()渲染函数。
        2. vm._update()更新函数。
      3. new Watcher内部会执行updateComponent,这次才真正执行updateComponent,执行函数内部的vm._render()渲染函数。
        1. 进入文件src\core\instance\render.js,执行Vue.prototype._render,渲染函数的目的是得到虚拟DOM。
          1. vnode = render.call(vm._renderProxy, vm.$createElement)获得虚拟DOM。
      4. 再执行lifecycle.js文件内执行vm._update()更新函数。
        1. 判断是否有虚拟DOM。
          1. 若没有则通过vm._patch_创建,虚拟DOM将变成真实DOM。
          2. 若有则通过vm._patch_更新。
    vue执行流程

    new Vue({})发生了什么?

    1. 选项的合并,用户选项和系统选项的合并。
    2. 组件实例相关的属性初始化,$parent,$root,$children,$refs
    3. 监听自定义事件。
    4. 解析插槽,$slots,$scopeSlots,$createElement()
    5. 注入祖辈传递下来的数据。
    6. 把自己内部的状态进行响应式的处理,props,methods,data,computed,watch
    7. 提供给后代,用来隔代传递参数

    Vue数据响应式

    1. Vue中利用了JS语言特性
      Object.defineProperty(),通过定义对象属性getter/setter拦截对属性的访问。
      具体实现是在Vue初始化时,会调用initState,它会初始化data,props等,这里着重关注data初始
      化。
    2. Vue1最大问题watcher过多,项目大到一定程度容易崩溃,所以导致Vue1性能很差。
    3. Vue2的解决方法,把watcher粒度降低,一个组件一个watcher。
    4. 如果有数据发生变化,通过Diff算法把两次计算的结果比较,才能知道那里变化并进行更新。
    5. Watcher和属性key之间的关系是1对N,正好跟Vue1的一对N是相反。
    6. 当用户自己编写表达式this.$watch('foo',function(){})时,会产生新的watcher实例,此时'foo'又跟多个watcer产生关系,N对N。
    7. 在依赖收集过程中相互添加关系,watcher.addDep()
    8. 一个对象一个Observer,内部有个大管家dep。
      1. 大管家负责对象新增或删除属性的更新通知。
    9. 一个key一个小管家dep。
      1. 小关节负责对应key的值变化的更新通知。


    相关文章

      网友评论

          本文标题:Vue vue2源码解析

          本文链接:https://www.haomeiwen.com/subject/meucaktx.html