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

87 阅读2分钟

1、前言

2、开始

2.1 创建新文件/开启服务

新建myVue文件夹并新建index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
</body>
</html>
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
    const vm = new Vue({
        data() {
           return {
            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
http-server . 
// 如果碰到端口被占用,也可以指定端口 
http-server -p 8081 .

2.2 开始调试

const vm = new Vue({这里打上断点,按F11进入构造函数

Snipaste_2022-09-19_17-31-43.png

进入构造函数内部,在this._init打上断点,按下F11进入函数

 function Vue (options) {
     // 检查是否通过new 关键字调用 Vue
     // instanceof 判断 Vue的原型对象是否在 this的原型链上
    if (!(this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    // 初始化
    this._init(options);
  }

这里只看initState函数,按F11进入

Snipaste_2022-09-22_16-09-12.png

这里重点看 initMethodsinitData, 打上断点F11进入函数内部

function initState (vm) {
    vm._watchers = [];
    var 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);
    }
  }

initMethods 函数内部

通过for...in 遍历 methods并检查是否符合条件

遍历methods 通过bind把this指向为new Vue实例

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
        );
      }
      // 检查props是否有值并检查props中的key跟methods中的key是否重复
      if (props && hasOwn(props, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a prop."),
          vm
        );
      }
      // 检查methods中的key是否与this实例中的关键字重复了
      if ((key in vm) && isReserved(key)) {
        warn(
          "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
          "Avoid defining component methods that start with _ or $."
        );
      }
    }
    // 把key值赋值到this上并且检查值是否非函数  并执行不同的逻辑
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
  }
}

methods中可以访问到this的关键函数 bind

根据浏览器对bind的兼容情况,一个正常使用bind一个通过 call和apply来实现bind

bind描述: bind()  方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

 /* istanbul ignore next */
function polyfillBind (fn, ctx) {
    // a 为函数调用时传的参数
  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;

initData 函数

 函数内部先检查 data 是否为function 是的话调用 getData 函数 返回 data对象
 赋值给 _data
 检查data对象是否为 object 不通过报错
 然后遍历key值 检查key值是否与methods与props中的key是否重复 不通过则报错
 最后通过 proxy函数进行代理
 
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 */);
 }

getData

调用data函数返回data对象

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

proxy 函数接收三个参数 this _data key 在this上创建新的属性, 并对该属性进行修改

当访问该属性时, this[sourceKey][key] 其实时访问this._data中的属性值 当操作该属性时, this[sourceKey][key] = val; 操作的也是this._data 中的属性值

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.3总结

为什么vue2中this可以直接获取到data与methods

methods 通过bindmethods函数的this改为new Vue实例 所以可以直接通过this访问 data 通过 Object.defineProperty() 函数对属性进行修改get以及set 当你this[key]调用的时候实际执行的时 this._data[key]