Why can Vue2 this directly obtain data and methods

1. Introduction

Hi, I’m Wakawa. Welcome to follow my official account Ruochuan Vision. Recently, I organized an activity to read the source code together. If you are interested, you can join me in wechat ruoChuan12 to communicate and learn and make progress together.

The learning Source Code Architecture Series includes jQuery, underscore, Lodash, Vuex, Sentry, Axios, Redux, KOa, VUUE-DevTools, and Vex4 over 10 source code articles. The three most recent are:

50 lines of serial Promise, that’s how the KOA Onion model was implemented, right?

Vue 3.2 has been released, so how did Yu Creek release vue.js?

Beginners can also understand Vue3 source code in those practical basic tool functions

Writing relatively difficult source code, consuming their own time and energy, and not getting many reading likes, is actually quite a frustrating thing. In terms of reading volume and benefit to readers, it does not promote the author’s continuous output of articles. So change your mind and write something relatively easy to understand. In fact, the source code is not so difficult to imagine, at least there are a lot of read understand. Goethe once said: reading a good book, is talking with noble people. The same can be said: read the source code, but also a way to learn and communicate with the author.

This article comes from a source read group friends in the group of questions, please ask @Ruochuan, “why the data in the data can be directly obtained with this ah”, when I read the source code to make a solution. Figured if someone asked me again, I’d have to answer it again. At that time, I thought I would write an article telling readers to explore the principles of their own, so this article came into being.

Reading this article, you will learn:

1.How to learn to debug vue2 source code2.Why can data in data be usedthisDirect access3.Why can methods in methods be usedthisDirect access4.Learn good code and ideas from the source code and put them into your own projectsCopy the code

This article is not difficult to use Vue can understand, I hope we start debugging and learn to see the source code.

Look at the source can be bold to guess, and finally carefully verify.

2. Example: This can get data and methods directly

As we all know, it is possible to export that I am Wakawa. Those who are curious will wonder why this is directly accessible.

const vm = new Vue({
    data: {
        name: 'I am Wakawa.',},methods: {
        sayName(){
            console.log(this.name); }}});console.log(vm.name); // I am Wakawa
console.log(vm.sayName()); // I am Wakawa
Copy the code

Then why can this. XXX obtain data in data and methods?

We construct our own written function, how to achieve a similar effect to Vue.

function Person(options){}const p = new Person({
    data: {
        name: 'if the sichuan'
    },
    methods: {
        sayName(){
            console.log(this.name); }}});console.log(p.name);
// undefined
console.log(p.sayName());
// Uncaught TypeError: p.sayName is not a function
Copy the code

How would you do that? With the problem, we come to debug Vue2 source code learning.

3. Prepare the environment to debug the source code to find out

To create the index. HTML file, create folder examples. Add the following js to .

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script>
    const vm = new Vue({
        data: {
            name: 'I am Wakawa.',},methods: {
            sayName(){
                console.log(this.name); }}});console.log(vm.name);
    console.log(vm.sayName());
</script>
Copy the code

Install NPM I-g HTTP-server globally to start the service.

npm i -g http-server
cd examples
http-server .
// If the port is occupied, you can specify the port
http-server -p 8081 .
Copy the code

This opens the index.html page you just wrote at http://localhost:8080/.

For those of you who are not familiar with debugging, check out this article “Debugger Debugging Tips that are easy to ignore on the front end”. The screenshots are very detailed.

Debug: Open debug in F12, source panel, in this case const VM = new Vue({set breakpoint.

After refreshing the page, press F11 to enter the function, at which point the breakpoint enters the Vue constructor.

3.1 Vue constructor

function Vue (options) {
    if(! (this instanceof Vue)
    ) {
        warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
}
/ / initialization
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
Copy the code

It is worth mentioning that if (! (this instanceof Vue) {} Check whether the constructor is called with the new keyword. Normally, we wouldn’t think of writing this.

Of course, you can call new from inside the function by looking at the source library. But vUE usually only needs new vue () once per project, so it’s not necessary.

JQuery source code is internal new, for the user there is no new construction.

jQuery = function( selector, context ) {
  // Return the object after new
  return new jQuery.fn.init( selector, context );
};
Copy the code

It’s called a lot with jQuery. JQuery can also be new. That’s the same effect as not using new.

If you don’t understand the usefulness of the new operator, look at my previous article. Can you simulate implementing JS’s new operator

Debugging: continue in this._init(options); Press F11 to enter the function.

3.2_init Initialization function

After going into the _init function, which is quite long and does quite a bit of work, we assume that the data and methods related implementation is in the initState(VM) function.

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

      // expose real self
      vm._self = vm;
      initLifecycle(vm);
      initEvents(vm);
      initRender(vm);
      callHook(vm, 'beforeCreate');
      initInjections(vm); // resolve injections before data/props
      // Initialization state
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      callHook(vm, 'created');
    };
}
Copy the code

Debug: Next we plan breakpoints in the initState(VM) function, press F8 to jump directly to the breakpoint, then F11 to enter the initState function.

3.3 initState Initialization state

From the function name, this function mainly implements the following functions:

Initialize props Initialize Methods Monitor data Initialize computed Initialize watchCopy the code
function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    // Methods are passed in
    if (opts.methods) { initMethods(vm, opts.methods); }
    // If data is passed in, initialize data
    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

We’ll focus on initializing methods and then data.

Debug: put a breakpoint on initMethods and a breakpoint on initData(VM). After viewing initMethods, press F8 to return to initData(VM). Continue F11 to enter the initMethods function.

3.4 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 functions, which mainly have some judgments.

Determine if each item in methods is a function, if not a warning. Determine if each item in methods conflicts with props, and warn if so. Determine if each item in methods is already innewVue instance vm exists, and the method name is reserved beginning with _ $(in JS generally refers to the internal variable identifier), if warning.Copy the code

Aside from these judgments, we can see that initMethods simply iterates over the methods object passed in and uses the bind binding function’s this to point to an instance object that is vm, i.e., new Vue.

This is why we can access functions in Methods directly through this.

We can hover over the bind variable and press Alt to see where the function is defined, which is line 218. Click here to jump to see the implementation of BIND.

3.4.1 Bind returns a function that modifies this to

function polyfillBind (fn, ctx) {
    function boundFn (a) {
      var l = arguments.length;
      return l
        ? l > 1
          ? fn.apply(ctx, arguments)
          : fn.call(ctx, a)
        : fn.call(ctx)
    }

    boundFn._length = fn.length;
    return boundFn
}

function nativeBind (fn, ctx) {
  return fn.bind(ctx)
}

var bind = Function.prototype.bind
  ? nativeBind
  : polyfillBind;
Copy the code

The old version does not support the native bind function. At the same time compatible writing method, the number of parameters to make a judgment, using call and apply implementation, said because of performance problems.

If you are not familiar with the use and implementation of call, apply, and bind, you can check whether you can simulate the JS call and apply methods

Debug: After viewing the initMethods function, press F8 to return to the breakpoint of initData(VM) function mentioned above.

3.5 initData Initializes data

The initData function is also some judgment. We mainly did the following things:

Assign _data first for later use. Warning that the data finally retrieved is not an object. Iterate through the data, warning each item if it conflicts with methods. If there is any conflict with props, warn. Instead of an internal private reserved property, do a layer of proxying to _data. Finally, monitor the data to make it responsive.Copy the code
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

3.5.1 getData Obtains data

Call the function when it is a function, and execute to get the object.

function getData (data, vm) {
    // #7573 disable dep collection when invoking data getters
    pushTarget();
    try {
      return data.call(vm, vm)
    } catch (e) {
      handleError(e, vm, "data()");
      return{}}finally{ popTarget(); }}Copy the code

3.5.2 proxy agent

You define an Object with Object.defineProperty

This. XXX is called this._data.xxx.

/** * Perform no operation. * Stubbing args to make Flow happy without leaving useless transpiled code * with ... rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). */
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

3.5.3 Object.defineProperty Defines Object attributes

Object.defineproperty is a very important API. There is also an API for defining multiple properties: Object.defineProperties(obj, props) (ES5)

Object. DefineProperty involves more important knowledge points and is often tested in interviews.

Value - The value returned when an attempt is made to get the property. Writable - Whether the property is writable. Enumerable -- This property is infor inWhether the loop will be enumerated. Signals - Specifies whether this property can be deleted.set() -- the function called by the update operation on this property. Get () - the function called to get the value of the property.Copy the code

See this link for detailed examples

3.6 Some functions in this paper will be explained in the end

3.6.1 Whether hasOwn is an attribute of the object itself

In debug mode, press Alt and hover over the method name to see where the function is defined. Click to jump.

/** * Check whether an object has the property. */
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
  return hasOwnProperty.call(obj, key)
}

hasOwn({ a: undefined }, 'a') // true
hasOwn({}, 'a') // false
hasOwn({}, 'hasOwnProperty') // false
hasOwn({}, 'toString') // false
// is a property of its own, not up the chain through the prototype.
Copy the code

3.6.2 isReserved Whether isReserved starts with $and _

/** * Check if a string starts with $ or _ */
function isReserved (str) {
  var c = (str + ' ').charCodeAt(0);
  return c === 0x24 || c === 0x5F
}
isReserved('_data'); // true
isReserved('$options'); // true
isReserved('data'); // false
isReserved('options'); // false
Copy the code

4. Use more than 60 lines of code to realize the simplified version

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);
}
function initData(vm){
  const data = vm._data = vm.$options.data;
  const keys = Object.keys(data);
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    proxy(vm, '_data', key); }}function initMethods(vm, methods){
  for (var key in methods) {
    vm[key] = typeofmethods[key] ! = ='function'? noop : methods[key].bind(vm); }}function Person(options){
  let vm = this;
  vm.$options = options;
  var opts = vm.$options;
  if(opts.data){
    initData(vm);
  }
  if(opts.methods){
    initMethods(vm, opts.methods)
  }
}

const p = new Person({
    data: {
        name: 'if the sichuan'
    },
    methods: {
        sayName(){
            console.log(this.name); }}});console.log(p.name);
// Undefined: undefined
// select * from *;
console.log(p.sayName());
// Uncaught TypeError: p.sayname is not a function
// select * from *;
Copy the code

5. To summarize

The basic knowledge involved in this paper is as follows:

The constructorthisPoints to call, bind, applyObjectDefineProperty, etc.Copy the code

This article is from the source of the solution to read group friends of doubt, through a detailed description of how to debug Vue source code, to explore the answer.

Answer the question at the beginning of the article:

The reason to access methods directly through this is because the methods in Methods specify this as an instance of new Vue (VM) through bind.

The reason for using this to access data directly from data is: The attributes in data will eventually be stored in the _data Object on the instance (VM) of new Vue. Access this. XXX, which is this._data.xxx after accessing the Object.defineProperty proxy.

The benefit of Vue’s design is accessibility. It is also inconvenient that props, methods and data are prone to conflict.

The overall difficulty of the article is not big, but it is very recommended that readers debug their own friends. After debugging, you may find: the original Vue source code, it is not as difficult as imagined, can also understand part of it.

Inspiration: When we work with common technologies and frameworks or libraries, be curious and think about internals. Be able to know what it is and why. I’m way ahead of a lot of people.

You may be wondering why this is omitted from template syntax when internal templates are compiled with. The reader who has the spare capacity can explore this principle.

Finally, you are welcome to join us in ruochuan12 to learn source code and make progress together.


About && communication groups

Recently, I organized a reading activity for source code. If you are interested, you can join me in wechat ruochuan12 for long-term communication and learning.

Author: Often in the name of ruochuan mixed traces in rivers and lakes. Welcome to add my wechat account ruochuan12. Front road | know very little, only good study. Concern about the public number if chuan vision, every week to learn the source code, learn to see the source code, advanced advanced front end. Wakawa’s blog segmentfault wakawa’s view column, has opened a column on wakawa’s view, welcome to follow ~ dig gold column, welcome to follow ~ github blog, beg a star^_^~