翻译进度
13
分块数量
0
参与人数

深入了解 Vue.js 源代码(#2):initMixin 方法
3

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。

1

This is the second post in a series taking a deep dive in the Vue.js source code. The ._init method is defined in the initMixin function. initMixin is called and passed the Vue object constructor function along with a list of other function calls immediately after the Vue object constructor function is defined:

initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);

The initMixin function is a simple function that takes the Vue object constructor function as a parameter and sets the ._init method on its prototype:

var uid$3 = 0;
function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    var vm = this;
    // a uid
    vm._uid = uid$3++;
var 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);
    }
// a flag to avoid this being observed
    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 {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    {
      initProxy(vm);
    }
    // expose real self
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    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);
    }
  };
}

A variable uid$3 is set to 0 outside the function in the top level scope to act as a counter that is incremented and set as a property on the Vue instance each time a new Vue instance is created and the ._init method is called.

var uid$3 = 0
function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    // a uid
    vm._uid = uid$3++;
    [. . . .]
  }
}

The ._init method sets a helper variable as this. This approach is identical to setting self = this by saving the current context of the this keyword to a variable for later use.

Vue.prototype._init = function (options) {
  var vm = this;
  [. . . .]
}

Next, the ._init method sets up a performance check.

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    var 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);
    }
    [. . . .]
  }
}

The ._init method declares two variables: startTag and endTag.

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    var startTag, endTag;
    [. . . .]
  }
}

Then you may notice an odd comment:

/* istanbul ignore if */

Istanbul is “Yet another JS code coverage tool that computes statement, line, function and branch coverage with module loader hooks to transparently add coverage when running tests.” The coverage hint tells Istanbul to ignore the if statement.

The if statement first checks whether the current environment is not the production environment.

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    /* 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);
    }
    [. . . .]
  }
}

The if statement then checks whether config.performance is set to true:

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    /* 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);
    }
    [. . . .]
  }
}

This leads us to a quick detour because the config object is set elsewhere and defaulted to false. As the comment introducing the config object’s performance property states, config.performance determines whether Vue records performance:

var config = ({
  [. . . .]
/**
   * Whether to record perf
   */
  performance: false,
[. . . .]
})

Turning back to the Vue.prototype._init method, the if statement next checks for a variable named mark.

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    /* 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);
    }
    [. . . .]
  }
}

This leads to another quick detour because the mark variable is defined elsewhere in the source code:

var mark;
var measure;
{
  var perf = inBrowser && window.performance;
  /* istanbul ignore if */
  if (
    perf &&
    perf.mark &&
    perf.measure &&
    perf.clearMarks &&
    perf.clearMeasures
  ) {
    mark = function (tag) { return perf.mark(tag); };
    measure = function (name, startTag, endTag) {
      perf.measure(name, startTag, endTag);
      perf.clearMarks(startTag);
      perf.clearMarks(endTag);
      perf.clearMeasures(name);
    };
  }
}

However, the mark variable will only be defined under certain circumstances. First, we check to see whether the environment is a browser:

// Browser environment sniffing
var inBrowser = typeof window !== 'undefined';
[. . . .]
{
  var perf = inBrowser && window.performance;
  [. . . .]
}

If the environment is a browser, the && chains the expression together so we check whether window.performance exists and set perf to window.performance if it does:

{
  var perf = inBrowser && window.performance;
  [. . . .]
}

Understanding what is going on here requires a quick dive in to the Window interface’s Performance property. According to MDN, the “The Window interface’s performance property returns a Performance object, which can be used to gather performance information about the current document. It serves as the point of exposure for the Performance Timeline API, the High Resolution Time API, the Navigation Timing API, the User Timing API, and the Resource Timing API.” The Performance interface is part of the High Resolution Timing API. It “provides access to performance-related information for the current page.”

mark, measure, clearMarks, and clearMeasures are methods on the Performance object.

  • The mark method creates “a timestamp in the browser’s performance entry buffer with the given name.”
  • The measure method creates “a named timestamp in the browser’s performance entry buffer between two specified marks (known as the start mark and end mark, respectively).”
  • The clearMarks method removes “the given mark from the browser’s performance entry buffer.”
  • The clearMeasures method removes “the given measure from the browser’s performance entry buffer.”

As the Vue.js API explains, if the performance option on config is set to true, it enables “component init, compile, render and patch performance tracing in the browser devtool performance/timeline panel” but only works in development mode and in browsers that support the performance.mark API.”

So let’s take a look back at the mark variable initialization:

{
  var perf = inBrowser && window.performance;
  /* istanbul ignore if */
  if (
    perf &&
    perf.mark &&
    perf.measure &&
    perf.clearMarks &&
    perf.clearMeasures
  ) {
    mark = function (tag) { return perf.mark(tag); };
    measure = function (name, startTag, endTag) {
      perf.measure(name, startTag, endTag);
      perf.clearMarks(startTag);
      perf.clearMarks(endTag);
      perf.clearMeasures(name);
    };
  }
}

So if the perf object exists and has mark, measure, clearMarks, and clearMeasures methods, then Vue sets mark and measure functions.

The mark function takes a tag as a parameter and returns a timestamp in the browser’s performance entry buffer with the tag as the name.

mark = function (tag) { return perf.mark(tag); };

Together with measure, this functions allow us to trace performance in the browser devtool performance/timeline panel.

Now that we have figured out what the mark function does, we are finally able to turn back to the Vue.prototype._init method and understand what is going on. The code below checks for a development environment, ensures that the performance config option is set to true, and ensures that a mark function exists. If so, Vue sets a start and end tag and calls the mark function passing the startTag as a parameter. This returns a timestamp in the browser’s performance entry buffer with the tag as the name.

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    var vm = this;
    // a uid
    vm._uid = uid$3++;
var 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);
    }
    [. . . .]
  }
}

The Vue.prototype._init method next sets a property on the Vue instance object called ._isVue and sets it to true as a flag in order to prevent it from being observed:

// a flag to avoid this being observed
vm._isVue = true;

The Vue.prototype._init method then checks to see whether options were passed as a parameter to the ._init method.

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
  [. . . .]
// 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 {
      vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );
    }
    [. . . .]
  }
}

As you will remember, the options parameter is passed to the Vue object constructor function when you create a new Vue instance — so the ._init method which is set on Vue.prototype has access to the parameter:

function Vue (options) {
  [. . . .]
}

After checking whether any options were passed, the ._init method next checks whether options._isComponent is set to true:

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);
}

options._isComponent is only set to true in one instance throughout the Vue.js source code — in the createComponentInstanceForVnode function:

function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent, // activeInstance in lifecycle state
  parentElm,
  refElm
) {
  var options = {
    _isComponent: true,
    parent: parent,
    _parentVnode: vnode,
    _parentElm: parentElm || null,
    _refElm: refElm || null
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  return new vnode.componentOptions.Ctor(options)
}

We will come back to the createComponentInstanceForVnode function later. Turning back to the ._init method, if options is passed and options._isComponent is true, the ._init method calls the initInternalComponent function and passes vm (i.e., this/Vue instance) and options as parameters:

function initMixin (Vue) {
  Vue.prototype._init = function (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);
    }
  }
}

The initInternalComponent function is declared elsewhere in the code:

function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);
  // doing this because it's faster than dynamic enumeration.
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;
  opts._parentElm = options._parentElm;
  opts._refElm = options._refElm;
var vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}

The initInternalComponent function sets a variable called opts and a property on the Vue object called $options equal to a new object with the vm.constructor.options set as __proto__ :

function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);
  [. . . .]
}

The initInternalComponent function then sets a number of properties on the new opts object by default. As the comment explains, it is faster to do this than dynamically enumerate and add properties.

function initInternalComponent (vm, options) {
  [. . . .]
  // doing this because it's faster than dynamic enumeration.
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;
  opts._parentElm = options._parentElm;
  opts._refElm = options._refElm;
var vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;
  [. . . .]
}

Finally, the initInternalComponent function sets two rendering related properties on the opts object if the options.render property is true:

function initInternalComponent (vm, options) {
  [. . . .]
  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}

Jumping out of the initInternalComponent function and back into the initMixin function, if options or options._isComponent are false, initMixin sets a $options property to the results of calling the mergeOptions function and passing three parameters: (1) a function called resolveConstructorOptions with vm.constructor (the constructor of the Vue instance) as a parameter; (2) options or an empty object; and (3) vm (the Vue instance).

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
  [. . . .]
// 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 {
      vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );
    }
    [. . . .]
  }
}

We will dive in to the mergeOptions function and keep working through initMixin in the next post. If you like the series and want to motivate me to keep working through it, clap, follow, respond, or share to your heart’s content.

Next Post:

A deep dive in the Vue.js source code (#3)

本文章首发在 Vuejs 知识社区
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/@oneminutejs/a-deep-d...

译文地址:https://vuejscaff.com/topics/117/in-dept...

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!