为什么Vue2 this能够直接获取到data和methods

发布时间:2021-09-24 17:53:24 作者:柒染
来源:亿速云 阅读:150

这篇文章将为大家详细讲解有关为什么Vue2 this能够直接获取到data和methods,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

1. 示例:this 能够直接获取到 data 和 methods

举例:

const vm = new Vue({
    data: {
        name: '我是若川',
    },
    methods: {
        sayName(){
            console.log(this.name);
        }
    },
});
console.log(vm.name); // 我是若川
console.log(vm.sayName()); // 我是若川

这样是可以输出我是若川的。好奇的人就会思考为啥 this 就能直接访问到呢。

那么为什么 this.xxx 能获取到data里的数据,能获取到 methods 方法。
我们自己构造写的函数,如何做到类似Vue的效果呢。

function Person(options){

}

const p = new Person({
    data: {
        name: '若川'
    },
    methods: {
        sayName(){
            console.log(this.name);
        }
    }
});

console.log(p.name);
// undefined
console.log(p.sayName());
// Uncaught TypeError: p.sayName is not a function

如果是你,你会怎么去实现呢。带着问题,我们来调试 Vue2源码学习。

2. 准备环境调试源码一探究竟

可以在本地新建一个文件夹examples,新建文件index.html文件。
<body></body>中加上如下js。

<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
    const vm = new Vue({
        data: {
            name: '我是若川',
        },
        methods: {
            sayName(){
                console.log(this.name);
            }
        },
    });
    console.log(vm.name);
    console.log(vm.sayName());
</script>

再全局安装npm i -g http-server启动服务。

npm i -g http-server
cd examples
http-server .
// 如果碰到端口被占用,也可以指定端口
http-server -p 8081 .

这样就能在http://localhost:8080/打开刚写的index.html页面了。

调试:在 F12 打开调试,source 面板,在例子中const vm = new Vue({打上断点。

为什么Vue2 this能够直接获取到data和methods

刷新页面后按F11进入函数,这时断点就走进了 Vue 构造函数。

2.1 Vue 构造函数

function Vue (options) {
    if (!(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);

值得一提的是:if (!(this instanceof Vue)){} 判断是不是用了 new 关键词调用构造函数。
一般而言,我们平时应该不会考虑写这个。
当然看源码库也可以自己函数内部调用 new 。但 vue 一般一个项目只需要 new Vue() 一次,所以没必要。
而 jQuery 源码的就是内部 new ,对于使用者来说就是无new构造。

jQuery = function( selector, context ) {
  // 返回new之后的对象
  return new jQuery.fn.init( selector, context );
};

因为使用 jQuery 经常要调用。
其实 jQuery 也是可以 new 的。和不用 new 是一个效果。

调试:继续在this._init(options);处打上断点,按F11进入函数。

2.2 _init 初始化函数

进入 _init 函数后,这个函数比较长,做了挺多事情,我们猜测跟datamethods相关的实现在initState(vm)函数里。

// 代码有删减
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
      //  初始化状态
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      callHook(vm, 'created');
    };
}

调试:接着我们在initState(vm)函数这里打算断点,按F8可以直接跳转到这个断点,然后按F11接着进入initState函数。

2.3 initState 初始化状态

从函数名来看,这个函数主要实现功能是:

function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    // 有传入 methods,初始化方法
    if (opts.methods) { initMethods(vm, opts.methods); }
    // 有传入 data,初始化 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);
    }
}

我们重点来看初始化 methods,之后再看初始化 data

调试:initMethods 这句打上断点,同时在initData(vm)处打上断点,看完initMethods函数后,可以直接按F8回到initData(vm)函数。继续按F11,先进入initMethods函数。

2.4 initMethods 初始化方法

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

initMethods函数,主要有一些判断。

除去这些判断,我们可以看出initMethods函数其实就是遍历传入的methods对象,并且使用bind绑定函数的this指向为vm,也就是new Vue的实例对象。

这就是为什么我们可以通过this直接访问到methods里面的函数的原因。

我们可以把鼠标移上 bind 变量,按alt键,可以看到函数定义的地方,这里是218行,点击跳转到这里看 bind 的实现。

2.4.1 bind 返回一个函数,修改 this 指向
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;

简单来说就是兼容了老版本不支持 原生的bind函数。同时兼容写法,对参数多少做出了判断,使用callapply实现,据说是因为性能问题。
如果对于callapplybind的用法和实现不熟悉,能否模拟实现JS的callapply方法

调试:看完了initMethods函数,按F8回到上文提到的initData(vm)函数断点处。

2.5 initData 初始化 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 */);
}
2.5.1 getData 获取数据

是函数时调用函数,执行获取到对象。

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();
    }
}
2.5.2 proxy 代理

其实就是用 Object.defineProperty 定义对象
这里用处是:this.xxx 则是访问的 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);
}
2.5.3 Object.defineProperty 定义对象属性

2.6 文中出现的一些函数,最后统一解释下

2.6.1 hasOwn 是否是对象本身拥有的属性
调试模式下,按alt键,把鼠标移到方法名上,可以看到函数定义的地方。点击可以跳转。
/**
   * 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
// 是自己的本身拥有的属性,不是通过原型链向上查找的。
2.6.2 isReserved 是否是内部私有保留的字符串$  和 _ 开头
/**
   * 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

3. 最后用60余行代码实现简化版

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] = typeof methods[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: '若川'
    },
    methods: {
        sayName(){
            console.log(this.name);
        }
    }
});

console.log(p.name);
// 未实现前: undefined
// '若川'
console.log(p.sayName());
// 未实现前:Uncaught TypeError: p.sayName is not a function
// '若川'

4. 总结

本文涉及到的基础知识主要有如下:

本文源于解答源码共读群友的疑惑,通过详细的描述了如何调试 Vue 源码,来探寻答案。
解答文章开头提问:
通过this直接访问到methods里面的函数的原因是:因为methods里的方法通过 bind 指定了this为 new Vue的实例(vm)。
通过 this 直接访问到 data 里面的数据的原因是:data里的属性最终会存储到new Vue的实例(vm)上的 _data对象中,访问 this.xxx,是访问Object.defineProperty代理后的 this._data.xxx。
Vue的这种设计,好处在于便于获取。也有不方便的地方,就是propsmethods 和 data三者容易产生冲突。
文章整体难度不大,但非常建议读者朋友们自己动手调试下。调试后,你可能会发现:原来 Vue 源码,也没有想象中的那么难,也能看懂一部分。

关于为什么Vue2 this能够直接获取到data和methods就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

推荐阅读:
  1. better-scroll方法怎么在vue2中使用
  2. vue2 前端搜索实现示例

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue2 this data

上一篇:寻找种子用户的方法有哪些

下一篇:python中数据类型的示例分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》