目錄
拋出問題
原始碼分析
首頁 web前端 Vue.js 聊聊vue2.x中this的指向問題,為什麼它指向vue實例?

聊聊vue2.x中this的指向問題,為什麼它指向vue實例?

Jan 20, 2022 am 10:23 AM
this

這篇文章帶大家聊聊vue2.x中this的指向問題,介紹一下this為什麼指向vue實例,希望對大家有幫助!

聊聊vue2.x中this的指向問題,為什麼它指向vue實例?

組內程式碼走查偶然提起為什麼this可以直接呼叫到data、methods和props、computed裡的值,然後大家都有一些猜想,但沒有一個明確的答案,為搞清這個問題,查閱了vue的源碼,有一些了解,寫個文章記錄一下。

拋出問題

正常開發vue程式碼,大差不差都會這麼寫

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

為什麼這裡的this.name可以直接存取data裡面定義的name呢,或是this.someFn可以直接存取methods裡定義的函數呢,帶著這個問題開始看vue2.x的源碼找答案。

原始碼分析

這裡先貼個vue的原始碼位址vue原始碼。我們先來看看vue實例的建構函數,建構子在原始碼的目錄/vue/src/core/instance/index.js下,程式碼量不多,全部貼出來看看

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
登入後複製

建構子很簡單,if (!(this instanceof Vue)){} 判斷是不是用了 new 關鍵字呼叫建構函數,沒有則拋出warning,這裡的this指的是Vue的一個實例。如果正常使用了new關鍵字,就走_init函數,是不是很簡單。

_init函數分析

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)
    }
  }
}
登入後複製

_init函數有點長,做了很多事情,這裡就不一一解讀,和我們這次探索相關的內容應該在initState(vm)這個函數中,我們繼續到initState這個函數裡看看。

initState函數分析

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)
  }
}
登入後複製

#可以看出initState做了5件事

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

我們先重點看看初始化methods做了什麼

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);
    }
}
登入後複製

initMethods主要是一些判斷:

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

除去上述說的這些判斷,最重要的就是在vue實例上定義了一遍methods裡所有的方法,並且使用bind函數將函數的this指向Vue實例上,就是我們new Vue()的實例物件上。

這就解釋了為啥this可以直接存取到methods裡的方法。

initData 初始化資料

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 */);
}
登入後複製

initdata做了哪些事情呢:

  • 先在實例_data 上賦值,getData函數處理data這個function,傳回的是一個物件
  • 判斷最終取得到的data, 不是物件給出警告。
  • 判斷methods裡的函數和data裡的key是否有衝突
  • 判斷props和data裡的key是否有衝突
  • 判斷是不是內部私有的保留屬性,若不是就做一層代理,代理到_data 上
  • 最後監聽data,使之成為響應式資料
##再看下proxy函數做了什麼:

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);
}
登入後複製
其實這裡的

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

  • 至於data的這種設計模式的優缺點,大家也可以繼續探索,畢竟不在此次問題討論之中。

【相關推薦:

vue.js影片教學

以上是聊聊vue2.x中this的指向問題,為什麼它指向vue實例?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

聊聊Vue2為什麼能透過this存取各種選項中屬性 聊聊Vue2為什麼能透過this存取各種選項中屬性 Dec 08, 2022 pm 08:22 PM

這篇文章帶大家解讀vue原始碼,來介紹一下Vue2中為什麼可以使用 this 存取各種選項中的屬性,希望對大家有幫助!

一篇搞懂this指向,追趕70%的前端人 一篇搞懂this指向,追趕70%的前端人 Sep 06, 2022 pm 05:03 PM

同事因為this指向的問題卡住的bug,vue2的this指向問題,使用了箭頭函數,導致拿不到對應的props。當我跟他介紹的時候他竟然不知道,隨後也刻意的看了一下前端交流群,至今最起碼還有70%以上的前端程式設計師搞不明白,今天給大家分享一下this指向,如果啥都沒學會,請給我一個大嘴巴子。

使用this關鍵字的巧妙方式在jQuery中 使用this關鍵字的巧妙方式在jQuery中 Feb 25, 2024 pm 04:09 PM

jQuery中this關鍵字的靈活運用在jQuery中,this關鍵字是一個非常重要且靈活的概念,它用來引用目前正在操作的DOM元素。透過合理的運用this關鍵字,我們可以方便地操作頁面中的元素,實現各種互動效果和功能。本文將結合具體的程式碼範例,介紹this關鍵字在jQuery中的靈活運用。簡單的this範例首先,我們來看一個簡單的this範例。假設我們有一

什麼是this?深入解析JavaScript中的this 什麼是this?深入解析JavaScript中的this Aug 04, 2022 pm 05:02 PM

什麼是this?以下這篇文章跟大家介紹一下JavaScript中的this,並聊聊this在函數不同呼叫方式下的區別,希望對大家有所幫助!

JavaScript如何改變this指向?三種方法淺析 JavaScript如何改變this指向?三種方法淺析 Sep 19, 2022 am 09:57 AM

JavaScript如何改變this指向?以下這篇文章跟大家介紹一下JS改變this指向的三種方法,希望對大家有幫助!

Java中this方法怎麼使用 Java中this方法怎麼使用 Apr 18, 2023 pm 01:58 PM

一、this關鍵字1.this的類型:哪個物件呼叫就是哪個物件的參考類型二、用法總結1.this.data;//存取屬性2.this.func();//存取方法3.this( );//呼叫本類別中其他建構方法三、解釋用法1.this.data這種是在成員方法中使用讓我們來看看不加this會出現什麼樣的狀況classMyDate{publicintyear;publicintmonth;publicintday; publicvoidsetDate(intyear,intmonth,intday){ye

JavaScript箭頭函數中的this詳解 JavaScript箭頭函數中的this詳解 Jan 25, 2024 pm 01:41 PM

JavaScript中箭頭函數是一種比較新的語法,沒有自己的this關鍵字,相反箭頭函數的this指向包含它的作用域對象,影響方面有:1、箭頭函數中的this是靜態的;2、箭頭函數不能作為構造函數使用;3、箭頭函數不能用作方法。

帶你去詳解 this 的四個綁定規則 帶你去詳解 this 的四個綁定規則 Nov 01, 2022 pm 05:49 PM

this 關鍵字是 JavaScript 中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在所有函數的作用域中。但即使是非常有經驗的 JavaScript 開發者也很難說清它到底指向什麼。

See all articles