首页>>前端>>Vue->为什么 Vue2 this 能够直接获取到 data 和 methods?

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

时间:2023-11-30 本站 点击:1

前言

Vue 作为国内炙手可热的前端框架,重要性不言而喻。尤其渐进式对前端新手都是开发体验非常友好的,因此将它的任何细节和特性单拎出来重点讲解,我认为都是不过分的。

在我们平时写 vue2 的时候,经常使用 this.xxx来访问 data 里面定义的数据,那么问题来了?按照我们以往的常识,这不太像是 JavaScript 自身的 this 特性吧?所以本次,我们来揭开它这层薄薄的面纱。

如果读者已经了解了该功能的实现原理,建议可以跳过文章的前半部分,不用浪费阅读时间。直接看下面 我的探究方法 段落,去了解一些不同的想法。

现象到本质

这里我们有一个数据 message ,有一个方法 showMessage 用来打印 message。问题在于:这里的 this 如果要访问 message 按理来说,应该是 取不到的,因为this指向methods? 而 vue 神奇的通过 this 直接拿到,难道有什么魔法嘛?

let vm = new Vue({  el:"#app",  data:{      message:'Deno'  },  methods:{    showMessage(){      debugger;      console.log(this.message)    }  }})

上述代码,我们先来 debugger 一下,看看这个地方的 this 到底有什么神奇之处?

呃,似乎看不出来有什么奇怪的地方。this不是methods,而是vm实例。而this 确实里面有 message 属性?而 _data 里面也有一份。从我们浅薄的概念里,_data 里这份不奇怪,奇怪的是 this 这份哪来的?接着往下翻,魔法就暴露出来了

发现没?调试工具告诉我们,this 身上的 message 是被劫持走的,所以真正的逻辑在这里,我们点击 FunctionLocation 进入到函数定义的源码部分:

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

从源码里,有经验的同学大概一眼可以看出端倪。但如果暂时没有看出来,也没关系。在这里打上断点,一探究竟!

可以很清晰的看到,这里最终就是使用了 _data 里面的值,它只是做了一层转发,最终使用了 Object.defineProperty(target, key, sharedPropertyDefinition);跟我们 vue2 的响应式原理其实是一样的。所以当我们获取 this 身上部分 data 里面定义的值,就可以直接拿到啦!是不是非常简单?

追本溯源

data

前面我很容易的就定位到了,根本原因。但是,前面的推理,其实是有 bug 的。细心的同学会嘀咕一件事:你怎么老在说 _data ,我代码里面明明写的是 data,这里是怎么在 vue 里面变成 _data 的呢?没讲清楚呀!

是的,这就是我们现在要解释的问题了。为了回答清楚这个问题,还是需要从 new 一个 vue 实例来说起了。

中间的过程,我想本文并不涉及太多,如果大家关注的话,可以看 若川 的引文。我这里直接调试到了最终这个逻辑的关键部分:

function initData (vm) {  var data = vm.$options.data;  data = vm._data = typeof data === 'function'    ? getData(data, vm)  : data || {};  ......

最关键的代码就是 data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; 我们可以看到,data 最后还是会赋值给 _data。

顺便我们可以了解一个知识点:vue 中 data 可能是一个函数。

getData 这里会执行一下这个函数,返回一个新的 data 对象。那为什么需要这么做呢?因为 vue 组件可能会有多个实例。如果共用一个 data 会导致内部混乱。所以每次返回一个新的 data 。这样每个组件都有自己的 data 。而根组件往往只有一个,所以我前面 new Vue 的时候 data 写不写成函数形式,都无妨。不过为了养成良好的习惯,这里建议还是写成函数形式。

methods

前面介绍完了 data ,接下来我们看看 methods 。如果大家有仔细调试前面的 data 源码部分,应该在中间很容易发现这样一段代码:

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 {      ......

很明显,methods 方法的处理就在 initMethods 函数中了。我们进去看看......

function initMethods (vm, methods) {    var props = vm.$options.props;    for (var key in methods) {      ......      vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);      ......    }  }

解释下这段逻辑:循环拿到 methods 里面的每一个方法名字,如果是个函数就执行 bind 函数。如果不是,则绑定一个空的 noop 函数(这里也能知道,如果 methods 写了无效参数,也会给一个兜底的函数)。

 function noop (a, b, c) {}

 /* istanbul ignore next */  function polyfillBind (fn, ctx) {    function boundFn (a) {      var l = arguments.length;      return l        ? l > 1          ? fn.apply(ctx, arguments)//大于1个参数,那么直接可以apply传数组了          : fn.call(ctx, a)        : fn.call(ctx)    }    boundFn._length = fn.length;//这是我比较佩服的地方,vue 很严谨还特意留了一份长度,函数的len一般都是参数。(普通形参才算,es6的默认参数用法出现,则包括它自身的后续都不算长度)。这里vue将length的值也修正了。但是注意,是修正给了下划线length。自身的length是不能修改的。    return boundFn  }  function nativeBind (fn, ctx) {    return fn.bind(ctx)//绑定实例 vm 成为方法的第一个参数  }  var bind = Function.prototype.bind    ? nativeBind    : polyfillBind;

在这里,我们很容易发现 vue 做的还是比较好的。它考虑到了如果不存在 bind 情况,那么就需要自己实现一个。

注释里也都写了,我想直接用一句话就可以概括实现的核心思想:bind 相当于一个未执行的函数里面存放着call/apply等待被执行,传入的第一个参数都是改变 this。 基于此逻辑实现的 bind 都是成立的。

至此,我们总算是解释完了标题里提到的这两点。

疑问扩展

为什么 methods 可以用这种逻辑赋值的方式给实例?而 data 却要用 Object.defineProperty 他两用一个逻辑不行吗?

data 不能学 methods 这样的处理逻辑:

methods 之所以这样赋值,由于函数本身是对象,赋值的是一个引用关系。而非真正的值拷贝。如果 data 使用这样的方式,如果里面储存的是原始值。那么就会导致产生副本了。这是一个糟糕的 bug !

methods 不能学 data Object.defineProperty 处理:

由于methods还需要处理this指向重新bind,这种是不太合适的。参考网上 Object.defineProperty 缺陷类文章。主要出于性能方面考量。能不用尽量不用。

我的探究方法

先学门外汉

当我们对框架里某个特性特别感兴趣的时候,大家的第一反应往往是看着漫长的源码,望而生畏,最后不了了之。

这样很容易放弃,我比较推荐的办法是:先理清这个特性如何使用,尽可能详细的分析出它的行为。一定要充分细致的了解,这样才能够调试的时候,迅速抓住问题的“根”。

学会借力

经常看到有同学看到问题。还没去网上查,自己先琢磨1-2小时,结果一看,人家早就写了文章清清楚楚的解答了,顿时心里是不是落差挺大的。比较推荐的办法是,遇到问题,百度或者 Google 并不丢人。不要吝啬使用自己的搜索引擎。如果这个办法行不通,大胆的群里找大佬们问。总之,面子不值钱,技术值钱哈(笑~)。

探索的路上并不孤独

有些问题很难描述给别人听,或者不知道怎么搜索答案。我想大家都会感到一种孤独或无助感,没有关系。试着去分解问题。开始源码调试前,务必先搞清楚自己是否对某个 API 已经熟练使用了。

在 debugger 的时候,可以像本文中这样,先从问题的点出发,反过来再去寻找哪里引发的。这种方式我承认确实反直觉,但是某些情况下真的很好用。

当 debugger 到了看不懂的地方,这个时候像同伴们发起提问,我想会更有效率一些。因为大家知道你卡的点在哪里了,而不是看到你漫长的文字描述问题。大佬们一般看代码比看文字要省力。

相信大家如果能理解我说的办法,那么找问题肯定是事半功倍的。如果有更好的方法论,也欢迎你跟我分享,我们共同进步!

原文:https://juejin.cn/post/7099387400207482917


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/3798.html