美文网首页前端开发那些事儿
new Vue发生了什么?(2)

new Vue发生了什么?(2)

作者: 云海成长 | 来源:发表于2021-06-24 23:25 被阅读0次

    上一篇文章我们了解了Vue是怎么一步步变得丰富起来的,从而形成我们的3大框架之一,接下来我们当然是要了解这个Vue是用来干嘛的?怎么发生作用的?本质上Vue就是一个构造函数,所以我们通过new Vue来探究,从这里也可以看出,我们用Vue框架搭建的项目,本质上是一个Vue实例

    new Vue

    我们引入vue,然后new Vue创建应用,那么,要探究发生了什么,我们要先找到Vue构造函数定义的地方,上一篇我们知道丰富Vue的文件有4个,它正是在文件src\core\instance\index.js定义的。

    如果你还无法确定,可以通过debugger来查看,像这样:


    debugger

    一般我们像这样创建vue实例,new Vue时传入不同的选项,如el, data, mounted等,我们的思路之一可以是Vue都是怎么处理我们传入的这些选项的。

    var vm = new Vue({
                el: '#el',
                data: {
                  ...
                },
                mounted() {
                    ...
                },
                methods: {
                   ....
                }
            .......
            })
    

    接下来我们看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)
    }
    

    非常简单是不是,只要符合条件,就执行_init方法,还记得这个_init文件怎么来的吗?上一篇我们已经讲过,通过执行initMixin(Vue),接下来我们看看_init发生了什么?

    this._init(options)

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
    // 设置uid
        vm._uid = uid++
    
        let startTag, endTag
        /* istanbul ignore if */
        // 性能相关的操作
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag) // 这里调用window.performance.mark:从navigetionStart事件发生时刻到记录时刻间隔的毫秒数
        }
    
        // a flag to avoid this being observed
       // 添加_isVue标记
        vm._isVue = true
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
        // 合并options,设置$options
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
       // _self执行自身
        vm._self = vm
        // 初始化生命周期
        initLifecycle(vm)
        // 初始化事件中心
        initEvents(vm)
        // 初始化渲染
        initRender(vm)
        callHook(vm, 'beforeCreate')
        // 初始化 data、props、computed、watcher 等等
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        // 调用钩子函数create
        callHook(vm, 'created')
    
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)  // 跟上面的性能操作相对应
        }
    
        if (vm.$options.el) {
          // 如果存在选择器,则执行挂载方法
          vm.$mount(vm.$options.el)
        }
      }
    }
    

    好了,让我们来总结如下:


    this._init(options)

    让我们来看看用颜色圈出的操作具体是什么意思?

    1. vm._uid

    2. vm._isVue = true

    3. vm.$options

    调用mergeOptions合并构造函数的options,如Vue.options,返回的结果赋给vm.$options

    4. initProxy(vm)

    设置vm._renderProxy = new Proxy(vm, handlers), vm._renderProxy是干嘛用的,暂时不理会。

    5. vm._self = vm

    6. initLifecycle(vm)

    function initLifecycle (vm) {
        var options = vm.$options;
    
        // locate first non-abstract parent
        var parent = options.parent;
        if (parent && !options.abstract) {
          while (parent.$options.abstract && parent.$parent) {
            parent = parent.$parent;
          }
          parent.$children.push(vm);
        }
        
       // 挂载上它的父节点,子节点,根节点
        vm.$parent = parent;
        vm.$root = parent ? parent.$root : vm;
        // 对以下属性初始化
        vm.$children = [];
        vm.$refs = {};
    
        vm._watcher = null;
        vm._inactive = null;
        vm._directInactive = false;
        vm._isMounted = false;
        vm._isDestroyed = false;
        vm._isBeingDestroyed = false;
      }
    

    7. initEvents(vm)

    function initEvents (vm) {
       // 初始化vm._events, vm._hasHookEvent
        vm._events = Object.create(null);
        vm._hasHookEvent = false;
        // init parent attached events
        // 如果listeners存在,则进行更新
        var listeners = vm.$options._parentListeners;
        if (listeners) {
          updateComponentListeners(vm, listeners);
        }
      }
    

    其实它后面是对新旧listeners进行更新,这里暂时不讨论。

    8. initRender(vm)

    这一步就跟渲染有关了,例如虚拟DOM,创建元素的方法,$slots等,另外,还设置了$attrs$listeners,并将它们设置为响应式

    function initRender (vm) {
        vm._vnode = null; // the root of the child tree
        vm._staticTrees = null; // v-once cached trees
        var options = vm.$options;
        var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree
        var renderContext = parentVnode && parentVnode.context;
        vm.$slots = resolveSlots(options._renderChildren, renderContext);
        vm.$scopedSlots = emptyObject;
        // bind the createElement fn to this instance
        // so that we get proper render context inside it.
        // args order: tag, data, children, normalizationType, alwaysNormalize
        // internal version is used by render functions compiled from templates
        vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
        // normalization is always applied for the public version, used in
        // user-written render functions.
        vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
    
        // $attrs & $listeners are exposed for easier HOC creation.
        // they need to be reactive so that HOCs using them are always updated
        var parentData = parentVnode && parentVnode.data;
    
        /* istanbul ignore else */
        {
          defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
            !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
          }, true);
          defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
            !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
          }, true);
        }
      }
    

    9. callHook(vm, 'beforeCreate')

    调用生命周期方法beforeCreate,在这里可以看到beforeCreate之前都做了哪些操作

    10. initInjections(vm)

    function initInjections (vm) {
        var result = resolveInject(vm.$options.inject, vm);
        if (result) {
          toggleObserving(false);
          Object.keys(result).forEach(function (key) {
            /* istanbul ignore else */
            {
              defineReactive(vm, key, result[key], function () {
                warn(
                  "Avoid mutating an injected value directly since the changes will be " +
                  "overwritten whenever the provided component re-renders. " +
                  "injection being mutated: \"" + key + "\"",
                  vm
                );
              });
            }
          });
          toggleObserving(true);
        }
      }
    

    这里对我们传入的选项inject进行处理。

    11. initState(vm)

    function initState (vm) {
        vm._watchers = [];
        var opts = vm.$options;
        if (opts.props) { initProps(vm, opts.props); }
        if (opts.methods) { initMethods(vm, opts.methods);  // 使用bind方法把methods里面的方法和vm绑定在一起,于是vm上有了这个方法,我们就可以用this.xxx()调用}
        if (opts.data) {
          initData(vm); // 1. 将data里面的数据定义在vm上,于是我们可以使用this.xxx访问属性;2. observe(data, true)将递归遍历data里面的转为可响应式数据,递归遍历到的数据如果是对象或数组,会被打上_ob_标记
        } else {
          // 这里将数据可响应化
          observe(vm._data = {}, true /* asRootData */);
        }
        if (opts.computed) { initComputed(vm, opts.computed); }
        if (opts.watch && opts.watch !== nativeWatch) {
          initWatch(vm, opts.watch);
        }
      }
    

    从代码可知,这一步初始化vm.watchers = [],初始化选项computed,watch,props,methods, data并将data里面的数据转为响应式的,主要处理数据相关的

    12. initProvide(vm)

    function initProvide (vm) {
        var provide = vm.$options.provide;
        if (provide) {
          vm._provided = typeof provide === 'function'
            ? provide.call(vm)
            : provide;
        }
      }
    

    非常明显,处理我们传入的provide选项。

    13. callHook(vm, 'created')

    调用生命周期钩子函数created, 说明这时已经初始化好数据,但是并没有把模板正在挂载在元素上,下一步$mount才开始挂载。

    14. vm.$mount(vm.$options.el)

    如果我们有传入el选项,则会Vue会自动帮我们调用$mount方法,否则需要我们手动去调用vm.$mount()

    var mount = Vue.prototype.$mount;
      Vue.prototype.$mount = function (
        el,
        hydrating
      ) {
        el = el && query(el);
    
        /* istanbul ignore if */
        if (el === document.body || el === document.documentElement) {
           warn(
            "Do not mount Vue to <html> or <body> - mount to normal elements instead."
          );
          return this
        }
    
        var options = this.$options;
        // resolve template/el and convert to render function
        if (!options.render) {
          var template = options.template;
          if (template) {
            if (typeof template === 'string') {
              if (template.charAt(0) === '#') {
                template = idToTemplate(template);
                /* istanbul ignore if */
                if ( !template) {
                  warn(
                    ("Template element not found or is empty: " + (options.template)),
                    this
                  );
                }
              }
            } else if (template.nodeType) {
              template = template.innerHTML;
            } else {
              {
                warn('invalid template option:' + template, this);
              }
              return this
            }
          } else if (el) {
            template = getOuterHTML(el);
          }
          if (template) {
            /* istanbul ignore if */
            if ( config.performance && mark) {
              mark('compile');
            }
            // 将模板转render函数
            var ref = compileToFunctions(template, {
              outputSourceRange: "development" !== 'production',
              shouldDecodeNewlines: shouldDecodeNewlines,
              shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
              delimiters: options.delimiters,
              comments: options.comments
            }, this);
            var render = ref.render;
            var staticRenderFns = ref.staticRenderFns;
            options.render = render;
            options.staticRenderFns = staticRenderFns;
    
            /* istanbul ignore if */
            if ( config.performance && mark) {
              mark('compile end');
              measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
            }
          }
        }
        return mount.call(this, el, hydrating)
      };
    

    很明显,这里重写了$mount,这个在上一篇中我们已经提及到了,具体$mount做了什么?由于是重点内容,我们将在下一篇继续探讨。

    相关文章

      网友评论

        本文标题:new Vue发生了什么?(2)

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