>  기사  >  웹 프론트엔드  >  vue2.x에서 이것의 포인팅 문제에 대해 이야기해 봅시다. 왜 이것이 vue 인스턴스를 가리키나요?

vue2.x에서 이것의 포인팅 문제에 대해 이야기해 봅시다. 왜 이것이 vue 인스턴스를 가리키나요?

青灯夜游
青灯夜游앞으로
2022-01-20 10:23:564063검색

이 글에서는 vue2.x의 this 포인팅 문제에 대해 설명하고, 이것이 vue 인스턴스를 가리키는 이유를 소개합니다. 모두에게 도움이 되기를 바랍니다.

vue2.x에서 이것의 포인팅 문제에 대해 이야기해 봅시다. 왜 이것이 vue 인스턴스를 가리키나요?

그룹 내의 코드 연습에서 이것이 왜 데이터, 메서드, props 및 계산의 값에 직접 호출될 수 있는지 언급했습니다. 그러자 모두가 추측을 했지만 명확한 대답은 없었습니다. 이 문제를 명확히 하려면 vue의 소스 코드를 이해한 후 이를 기록하는 기사를 작성하겠습니다.

질문하기

일반적으로 vue 코드를 개발하면 거의 항상 이렇게 작성합니다

export default {
    data() {
        return {
            name: '彭鱼宴'
        }
    },
    methods: {
        greet() {
            console.log(`hello, 我是${this.name}`)
        }
    }
}

여기서 this.name이 데이터에 정의된 이름에 직접 액세스할 수 있거나, this.someFn이 정의된 메서드에 직접 액세스할 수 있는 이유는 무엇입니까? 이 질문을 염두에 두고 vue2.x의 소스 코드를 살펴보기 시작했습니다.

소스코드 분석

vuevue 소스코드의 소스코드 주소입니다. 먼저 vue 인스턴스의 생성자를 살펴보겠습니다. 생성자는 소스 코드 디렉토리인 /vue/src/core/instance/index.js에 있습니다. 모두 게시하고 살펴보세요. 생성자는 매우 간단합니다. if (!(this 인스턴스of Vue)){} new 키워드가 생성자를 호출하는 데 사용되는지 여부를 결정합니다. 그렇지 않으면 경고가 발생합니다. . 여기 가 있습니다. Vue의 인스턴스를 나타냅니다. new 키워드를 정상적으로 사용한다면 _init 함수를 사용하면 됩니다. 아주 간단하죠?

if (!(this instanceof Vue)){} 判断是不是用了 new 关键词调用构造函数,没有则抛出warning,这里的this指的是Vue的一个实例。如果正常使用了new关键词,就走_init函数,是不是很简单。

_init函数分析

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(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)

export default Vue

_init函数有点长,做了很多事情,这里就不一一解读,和我们此次探索相关的内容应该在initState(vm)这个函数中,我们继续到initState这个函数里看看。

initState函数分析

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // 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
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== &#39;production&#39;) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, &#39;beforeCreate&#39;)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, &#39;created&#39;)

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

可以看出initState做了5件事情

  • 初始化props
  • 初始化methods
  • 初始化data
  • 初始化computed
  • 初始化watch

我们先重点看看初始化methods做了什么

initMethods 初始化方法

export function initState (vm: Component) {
  vm._watchers = []
  const 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主要是一些判断:

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

除去上述说的这些判断,最重要的就是在vue实例上定义了一遍methods里所有的方法,并且使用bind函数将函数的this指向Vue实例上,就是我们new Vue()的实例对象上。

这就解释了为啥this可以直接访问到methods里的方法。

initData 初始化数据

判断methods中定义的函数是不是函数,不是函数就抛warning;
判断methods中定义的函数名是否与props冲突,冲突抛warning;
判断methods中定义的函数名是否与已经定义在Vue实例上的函数相冲突,冲突的话就建议开发者用_或者$开头命名;

initdata做了哪些事情呢:

  • 先在实例 _data 上赋值,getData函数处理 data 这个 function,返回的是一个对象
  • 判断最终获取到的 data, 不是对象给出警告。
  • 判断methods里的函数和data里的key是否有冲突
  • 判断props和data里的key是否有冲突
  • 判断是不是内部私有的保留属性,若不是就做一层代理,代理到 _data 上
  • 最后监听data,使之成为响应式数据

再看下proxy函数做了什么:

function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === &#39;function&#39;
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      warn(
        &#39;data functions should return an object:\n&#39; +
        &#39;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&#39;,
        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 */);
}

其实这里的Object.defineProperty就是用来定义对象的

proxy的用处就是使this.name指向this._data.name

剩下的observe函数不在此次探讨范围,感兴趣的朋友可以自己去看看源码。

总结

回到一开始抛出的问题做一个解答:

  • methods里的方法通过 bind 指定了this为 new Vue的实例(vm),methods里的函数也都定义在vm上了,所以可以直接通过this直接访问到methods里面的函数。

  • data函数返回的数据对象也都存储在了new Vue的实例(vm)上的_data上了,访问 this.name时实际访问的是Object.defineProperty代理后的 this._data.name_init 함수 분석

  • 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);
    }
_init 함수는 좀 길고 할 일이 많아서 하나씩 설명하지는 않겠습니다. 이번에는 내용이 initState(vm) 함수에 있어야 합니다.

initState 함수 분석rrreee

initState가 5가지 일을 하는 것을 볼 수 있습니다🎜
  • props 초기화🎜
  • 초기화 방법
  • 데이터 초기화 데이터
  • 컴퓨팅 초기화
  • 초기화 초기화 방법이 초기화 초기화 방법에 대한 초기화 초기화 초기화 초기화 초기화 방법 🎜🎜rrreee🎜initmethods는 주로 판단입니다. 위의 판단에서 🎜가장 중요한 것은 vue 인스턴스의 메소드에 있는 모든 메소드를 정의하고 바인드 함수를 사용하여 함수의 this를 새로운 Vue의 인스턴스 객체인 Vue 인스턴스🎜로 지정하는 것입니다. () . 🎜🎜이것이 메소드의 메소드에 직접 액세스할 수 있는 이유를 설명합니다. 🎜🎜🎜initData는 데이터를 초기화합니다🎜🎜rrreee🎜initdata의 기능: 🎜
    • 먼저 _data 인스턴스에 값을 할당하고, getData 함수는 데이터 함수를 처리하고 객체를 반환합니다.🎜
    • 판단 최종 획득한 데이터는 객체가 아니며 경고가 발생합니다. 🎜
    • 메서드의 함수와 데이터의 키 사이에 충돌이 있는지 확인🎜
    • props와 데이터의 키 사이에 충돌이 있는지 확인🎜
    • 내부 비공개 예약 속성인지 확인 , 그렇지 않은 경우 레이어 생성 프록시, _data에 대한 프록시🎜
    • 마지막으로 데이터를 수신하여 반응형 데이터로 만듭니다🎜🎜🎜프록시 기능이 수행하는 작업을 살펴보겠습니다.🎜rrreee🎜실제로 Object.defineProperty 여기는
      🎜🎜객체를 정의하는 데 사용되는프록시의 목적은 this.namethis._data.name을 가리키도록 만드는 것입니다. 🎜🎜나머지 관찰 기능은 이 토론의 범위에 포함되지 않습니다. 관심 있는 친구는 소스 코드를 직접 확인해 볼 수 있습니다. 🎜🎜🎜요약🎜🎜🎜처음에 제기한 질문으로 돌아가서 답변해 주세요. 🎜
      • 🎜메소드의 메소드 code> <code>bind를 통해 this는 새로운 Vue(vm)의 인스턴스로 지정되고 메소드의 함수도 에 정의됩니다. >vm이므로 this를 통해 메서드의 함수에 직접 액세스할 수 있습니다. 🎜🎜
      • 🎜data 함수에서 반환된 데이터 개체는 새 Vue 인스턴스(vm)의 _data에도 저장됩니다. this.name에 액세스하면 실제로 액세스되는 것은 Object.defineProperty 프록시 다음의 this._data.name입니다. 🎜🎜🎜🎜 이 데이터 디자인 패턴의 장점과 단점에 대해서는 계속해서 살펴볼 수 있지만 결국 이는 이 토론의 일부가 아닙니다. 🎜🎜【관련 추천: 🎜vue.js 동영상 튜토리얼🎜】🎜

위 내용은 vue2.x에서 이것의 포인팅 문제에 대해 이야기해 봅시다. 왜 이것이 vue 인스턴스를 가리키나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제