preface

The code check in the group occasionally mentioned why this could be called directly in the value of data, methods and props, and computed, and we all had some guesses, but no clear answer. In order to find out this problem, I consulted the source code of Vue and wrote an article to record it.

Throw out problem

Normal development of VUE code, most of it will be written like this

export default {
    data() {
        return {
            name: 'Pengyu Feast'}},methods: {
        greet() {
            console.log(` hello, I amThe ${this.name}`)}}}Copy the code

This. name can be accessed directly from data (name), or this.someFn can be accessed directly from methods (name).

Source code analysis

Here first paste a VUE source address VUE source code. Let’s look at the vue instance constructors, the constructor in the source directory/vue/SRC/core/instance/index. The js, the amount of code is not much, all posted and have a look

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)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
Copy the code

The constructor is simple, if (! (this instanceof Vue) {} Check if the constructor is called with the new keyword, if not, raise warning. If the new keyword is used normally, the _init function is used.

_init function analysis

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this
    // a 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)
    }

    // 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 */
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = 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)
    }
  }
}
Copy the code

The _init function is a little long, and it does a lot of things that I’m not going to go through here, but the only thing that’s relevant to our exploration is in the initState(VM) function, so let’s move on to the initState function.

InitState function analysis

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)
  if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code

You can see that initState does five things

  • Initialize the props
  • Initialize the methods
  • Initialize the data
  • Initialize the computed
  • Initialize the watch

Let’s focus first on what initialize methods does

InitMethods Initialization methods

function initMethods (vm, methods) {
    var props = vm.$options.props;
    for (var key in methods) {
      {
        if (typeofmethods[key] ! = ='function') {
          warn(
            "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
            "Did you reference the function correctly?",
            vm
          );
        }
        if (props && hasOwn(props, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a prop."),
            vm
          );
        }
        if ((key in vm) && isReserved(key)) {
          warn(
            "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
            "Avoid defining component methods that start with _ or $."
          );
        }
      }
      vm[key] = typeofmethods[key] ! = ='function'? noop : bind(methods[key], vm); }}Copy the code

InitMethods are mainly judgments:

If a function defined in methods is a function, throw warning. Check whether function names defined in methods conflict with props, and throw warning. Determine if a function name defined in methods conflicts with a function already defined on a Vue instance. In case of a conflict, the developer is advised to use the name starting with _ or $.Copy the code

The most important thing is to define all methods on a vue instance and use bind to refer this to the vue instance, which is our new vue () instance object.

This explains why this can access methods directly.

InitData Initializes data

function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {};
    if(! isPlainObject(data)) { data = {}; warn('data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    // proxy data on instance
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a data property."), vm ); }}if (props && hasOwn(props, key)) {
        warn(
          "The data property \"" + key + "\" is already declared as a prop. " +
          "Use prop default value instead.",
          vm
        );
      } else if(! isReserved(key)) { proxy(vm,"_data", key); }}// observe data
    observe(data, true /* asRootData */);
}
Copy the code

What does initData do?

  • The getData function handles the data function and returns an object
  • Warning if the data is not an object.
  • Check whether a function in methods conflicts with a key in data
  • Check whether the keys in props and data conflict
  • Determine if it is an internal private reserved property, if not, delegate it to _data
  • Finally, listen on the data to make it responsive

Let’s see what the proxy function does:

function noop (a, b, c) {}
var sharedPropertyDefinition = {
    enumerable: true.configurable: true.get: noop,
    set: noop
};

function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

Copy the code

Object. DefineProperty is used to define objects

The use of proxy is to make this.name point to this._data.name

The rest of the observe function is beyond the scope of this discussion. Interested friends can check the source code for themselves.

conclusion

Go back to the first question and answer it:

  1. The methods in Methods use bind to specify this as an instance of a new Vue (VM). The methods in methods are also defined on the VM, so you can access methods directly through this.

  2. The data Object returned by the data function is also stored in _data on the instance (VM) of new Vue. The this._data. Name is actually called after the Object.defineProperty proxy.

As for the advantages and disadvantages of Data’s design pattern, you can continue to explore, after all, not in this discussion.