您当前的位置:首页 > 网站建设 > javascript
| php | asp | css | H5 | javascript | Mysql | Dreamweaver | Delphi | 网站维护 | 帝国cms | React | 考试系统 | ajax | jQuery | 小程序 |

源码揭秘为什么 Vue2 this 能够直接获取到 data 和 methods

51自学网 2022-02-21 13:41:18
  javascript

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);// undefinedconsole.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-servercd exampleshttp-server .// 如果碰到端口被占用,也可以指定端口http-server -p 8081 .

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

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

刷新页面后按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 初始化状态

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

  • 初始化 props
  • 初始化 methods
  • 监测数据
  • 初始化 computed
  • 初始化 watch
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函数,主要有一些判断。

  • 判断 methods 中的每一项是不是函数,如果不是警告。
  • 判断 methods 中的每一项是不是和 props 冲突了,如果是,警告。
  • 判断 methods 中的每一项是不是已经在 new Vue实例 vm 上存在,而且是方法名是保留的 _ $ (在JS中一般指内部变量标识)开头,如果是警告。

除去这些判断,我们可以看出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

  • initData 函数也是一些判断。主要做了如下事情:
  • 先给 _data 赋值,以备后用。
  • 最终获取到的 data 不是对象给出警告。
  • 遍历 data ,其中每一项:
  • 如果和 methods 冲突了,报警告。
  • 如果和 props 冲突了,报警告。
  • 不是内部私有的保留属性,做一层代理,代理到 _data 上。
  • 最后监测 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 定义对象属性

万事OK自学网:51自学网_软件自学网_CAD自学网自学excel、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。