Home  >  Article  >  Web Front-end  >  Let's talk about the pointing problem of this in vue2.x. Why does it point to the vue instance?

Let's talk about the pointing problem of this in vue2.x. Why does it point to the vue instance?

青灯夜游
青灯夜游forward
2022-01-20 10:23:564074browse

This article will talk about the pointing problem of this in vue2.x, and introduce why this points to the vue instance. I hope it will be helpful to everyone!

Let's talk about the pointing problem of this in vue2.x. Why does it point to the vue instance?

The code walkthrough in the group accidentally mentioned why this can be directly called to the values ​​in data, methods, props, and computed. Then everyone had some guesses, but none of them were clear. In order to clarify this question, I checked the source code of vue. I have some understanding and wrote an article to record it.

Throw a question

Normally develop vue code, almost always write it like this

export default {
    data() {
        return {
            name: '彭鱼宴'
        }
    },
    methods: {
        greet() {
            console.log(`hello, 我是${this.name}`)
        }
    }
}

Why can this.name here directly access data? What about the name defined in, or this.someFn can directly access the function defined in methods? With this question, I started to look at the source code of vue2.x to find the answer.

Source code analysis

Here is the source code address of vuevue source code. Let’s first take a look at the constructor of the vue instance. The constructor is in the source code directory/vue/src/core/instance/index.js. There is not much code. Post it all to see.

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

The constructor is very Simple, if (!(this instanceof Vue)){} Determine whether the new keyword is used to call the constructor, if not, a warning will be thrown, here is this refers to an instance of Vue. If the new keyword is used normally, just use the _init function. Isn’t it very simple?

_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 !== &#39;production&#39; && 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 !== &#39;production&#39;) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, &#39;beforeCreate&#39;)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, &#39;created&#39;)

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== &#39;production&#39; && 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)
    }
  }
}

_The init function is a bit long and does a lot of things, so I won’t explain it one by one here, and we will explore it this time The relevant content should be in the initState(vm) function. Let's continue 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)
  }
}

It can be seen that initState has done 5 things

  • Initialize props
  • Initialization methods
  • Initialization data
  • Initialization computed
  • Initialization watch

Let’s first focus on what the initialization methods do

initMethods initialization method

function initMethods (vm, methods) {
    var props = vm.$options.props;
    for (var key in methods) {
      {
        if (typeof methods[key] !== &#39;function&#39;) {
          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] = typeof methods[key] !== &#39;function&#39; ? noop : bind(methods[key], vm);
    }
}

initMethods mainly consists of some judgments:

判断methods中定义的函数是不是函数,不是函数就抛warning;
判断methods中定义的函数名是否与props冲突,冲突抛warning;
判断methods中定义的函数名是否与已经定义在Vue实例上的函数相冲突,冲突的话就建议开发者用_或者$开头命名;

Except for the above judgments, the most important thing is in vue All methods in methods are defined on the instance, and the bind function is used to point the function's this to the Vue instance, which is the instance object of our new Vue().

This explains why this can directly access the methods in methods.

initData initializes data

function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === &#39;function&#39;
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      warn(
        &#39;data functions should return an object:\n&#39; +
        &#39;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&#39;,
        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 */);
}

What does initdata do:

  • First assign a value to the instance_data, and the getData function processes the data This function returns an object
  • to judge the data finally obtained, not the object to give a warning.
  • Determine whether the function in methods conflicts with the key in data
  • Determine whether there is a conflict between props and the key in data
  • Determine whether it is an internal private reserved attribute. If not, make a layer of proxy and proxy it to _data
  • Finally listen to the data and make it responsive data

Let’s take a look at 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);
}

In fact, the Object.defineProperty here is used to define the object. The purpose of

proxy is to use this.namePoint tothis._data.name

The remaining observe functions are not within the scope of this discussion. Interested friends can check out the source code themselves.

Summary

Go back to the question raised at the beginning and give an answer:

  • methods The method in bind specifies this as an instance of new Vue (vm), and the functions in methods are also defined in vm is installed, so you can directly access the functions in methods through this.

  • dataThe data objects returned by the function are also stored in _data## on the new Vue instance (vm) #Up, when accessing this.name, what is actually accessed is Object.defineProperty after proxy this._data.name.

As for the advantages and disadvantages of this design pattern of data, you can continue to explore, after all, it is not part of this discussion.

[Related recommendations:

vue.js video tutorial]

The above is the detailed content of Let's talk about the pointing problem of this in vue2.x. Why does it point to the vue instance?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete