首頁 > 軟體

Vue自定義指令中無法獲取this的問題及解決

2022-08-15 14:01:34

自定義指令中無法獲取this

問題

最近在使用自定義指令時遇到一個問題,我想在指令裡通過this直接去存取vue範例資料,但是顯示未定義,經大佬提醒,裡面的this很可能不是指向vue範例

解決方法

在函數裡增加第三個引數vnode,vnode.context就是指向當前的vue範例

總結

指令裡的this不是指向vue範例,可以使用vnode.context獲取this

自定義指令可傳入以下引數

  • el:指令所繫結的元素,可以用來直接操作 DOM。
  • binding:一個物件,包含以下 property:
  • name:指令名,不包括 v- 字首。
  • value:指令的繫結值,例如:v-my-directive=“1 + 1” 中,繫結值為 2。
  • oldValue:指令繫結的前一個值,僅在 update 和 componentUpdated 勾點中可用。無論值是否改變都可用。
  • expression:字串形式的指令表示式。例如 v-my-directive=“1 + 1” 中,表示式為 “1 + 1”。
  • arg:傳給指令的引數,可選。例如 v-my-directive:foo 中,引數為 “foo”。
  • modifiers:一個包含修飾符的物件。例如:v-my-directive.foo.bar 中,修飾符物件為 { foo: true, bar: true }。
  • vnode:Vue 編譯生成的虛擬節點。
  • oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 勾點中可用。

Vue使用this的這幾個坑你都知道嗎

最近寫vue專案遇到很多this指向的問題,今天來寫一下我總結的this指向

看了很多文章、部落格,對於正常函數,誰呼叫的它,this就指向誰,而箭頭函數沒有this,它的this指向一般就是上下文中,與誰呼叫它沒關係。

但是在Vue範例中,methods、生命週期函數中如果用的是正常函數,那麼它的this就指向Vue範例,也就是vm(本文中的vm是指const vm = new Vue({···})中的vm);如果是箭頭函數,在非嚴格模式下this就指向window物件,嚴格模式下是undefind。

這裡我分別來說一下普通函數中的this和Vue中的this

一、普通函數

普通函數的this是由動態作用域決定,它總指向於它的直接呼叫者。具體可以分為以下四項:this總是指向它的直接呼叫者, 例如 obj.func() ,那麼func()裡的this指的是obj。在預設情況(非嚴格模式,未使用 'use strict'),如果函數沒有直接呼叫者,this為window;在嚴格模式下,如果函數沒有直接調者,this為undefined使用call,apply,bind繫結的,this指的是繫結的物件

簡單總結: 

(1)全域性函數中的this指向window

(2)物件中的方法(函數)中的this,指向物件,理解:obj.m=function(){},m和fn等價,因此呼叫m也相當於呼叫fn,原理同3)

(3)建構函式中的this指向呼叫該建構函式的範例物件

(4)特殊this指向: 箭頭函數沒有繫結this,this繼承自外圍作用域,理解:檢視上一層級的函數的this的指向,繼承它!!

(5)繫結this指向:apply,call,bind繫結的物件

接下來用範例來介紹下各種this問題

1.全域性環境下,this 始終指向全域性物件(window), 無論是否嚴格模式

    console.log(this.document === document); // true
    // 在瀏覽器中,全域性物件為 window 物件:
    console.log(this === window); // true
    this.a = 3;
    console.log(window.a); // 3

2.函數直接呼叫,普通函數內部的this分兩種情況,嚴格模式和非嚴格模式

    //嚴格模式下, this為undefined
    function f2(){
      "use strict"; // 這裡是嚴格模式
      return this;
    }
    f2() === undefined; // true
    //而非嚴格模式下,this 預設指向全域性物件window
    function f1(){
      return this;
    }
    f1() === window; // true

     3.物件中的this,物件內部方法的this指向呼叫這些方法的物件

    //函數的定義位置不影響其this指向,this指向只和呼叫函數的物件有關。
    //多層巢狀的物件,內部方法的this指向離被呼叫函數最近的物件(window也是物件,其內部物件調
    //用方法的this指向內部物件, 而非window)。
    //例一:
    var obj = {
      prop: 37,
      f: function() {
        return this.prop;
      }
    };
    console.log(obj.f());  //37
    var a = obj.f;
    console.log(a());  //undefined
    
    var obj = {prop: 37};
    
    function independent() {
      return this.prop;
    }
    
    obj.f = independent;
    
    console.log(obj.f()); //37
    
    //例二:
    obj.b = {
      num: independent,
      prop: 42
    };
    console.log(obj.b.num()); //42

4.原型鏈中this,原型鏈中的方法的this仍然指向呼叫它的物件 

    var obj = {
      f : function(){ 
        return this.a + this.b; 
      }
    };
    var p = Object.create(obj);
    p.a = 1;
    p.b = 4;
    
    console.log(p.f()); // 5
//在p中沒有屬性f,當執行p.f()時,會查詢p的原型鏈,找到 f 函數並執行,但這與函數內部this指向物件 //p 沒有任何關係,只需記住誰呼叫指向誰。
 
//以上對於函數作為getter & setter 呼叫時同樣適用。

5.建構函式中this,建構函式中的this與被建立的新物件繫結 

注意:當構造器返回的預設值是一個this參照的物件時,可以手動設定返回其他的物件,如果返回值不是一個物件,返回this。

6.call & apply

當函數通過Function物件的原型中繼承的方法 call() 和 apply() 方法呼叫時, 其函數內部的this值可繫結到 call() & apply() 方法指定的第一個物件上, 如果第一個引數不是物件,JavaScript內部會嘗試將其轉換成物件然後指向它。

7.bind 方法

bind方法在ES5引入, 在Function的原型鏈上, Function.prototype.bind。通過bind方法系結後, 函數將被永遠繫結在其第一個引數物件上, 而無論其在什麼情況下被呼叫。

8.DOM事件處理常式,當函數被當做監聽事件處理常式時, 其 this 指向觸發該事件的元素 (針對於addEventListener事件)

  // 被呼叫時,將關聯的元素變成藍色
    function bluify(e){
      console.log(this);//在控制檯列印出所點選元素
      e.stopPropagation();//阻止時間冒泡
      e.preventDefault();//阻止元素的預設事件    
      this.style.backgroundColor = '#A5D9F3';
    }
    var elements = document.getElementsByTagName('*');// 獲取檔案中的所有元素的列表
    // 將bluify作為元素的點選監聽函數,當元素被點選時,就會變成藍色
    for(var i=0 ; i<elements.length ; i++){
      elements[i].addEventListener('click', bluify, false);
    }

9.內聯事件,內聯事件中的this指向分兩種情況:

  • 當程式碼被內聯處理常式呼叫時,它的this指向監聽器所在的DOM元素
  • 當程式碼被包括在函數內部執行時,其this指向等同於函數直接呼叫的情況,即在非嚴格模式指向全域性物件window, 在嚴格模式指向undefined。

10.setTimeout & setInterval,對於延時函數內部的回撥函數的this指向全域性物件window(當然我們可以通過bind方法改變其內部函數的this指向) 

    //預設情況下
    function Person() {  
        this.age = 0;  
        setTimeout(function() {
            console.log(this);
        }, 3000);
    }
    //通過bind繫結
    function Person() {  
        this.age = 0;  
        setTimeout((function() {
            console.log(this);
        }).bind(this), 3000);
    }
    var p = new Person();//3秒後返回建構函式新生成的物件 Person{...}

11.箭頭函數中的 this,由於箭頭函數不繫結this, 它會捕獲其所在(即定義的位置)上下文的this值, 作為自己的this值 

    // call() / apply() / bind() 方法對於箭頭函數來說只是傳入引數,對它的 this 毫無影響。
    //考慮到 this 是詞法層面上的,嚴格模式中與 this 相關的規則都將被忽略。(可以忽略是否在嚴格
    //模式下的影響)
    //因為箭頭函數可以捕獲其所在上下文的this值 所以:
    function Person() {  
        this.age = 0;  
        setInterval(() => {
            this.age++;// 回撥裡面的 `this` 變數就指向了期望的那個物件了
        }, 3000);
    }
    var p = new Person();
    //以上程式碼可以得到我們所以希望的值,下圖可以看到,在setTimeout中的this指向了建構函式新生成
    //的物件,而普通函數指向了全域性window物件

二、Vue中的this

1.Vue methods

來看看官方檔案給出的解釋:

methods 將被混入到 Vue 範例中。可以直接通過 範例vm 存取這些方法,或者在指令表示式中使用。方法中的 this自動繫結為 Vue 範例(vm)。

注意,不應該使用箭頭函數來定義 method 函數 (例如 plus: () => this.a++)。理由是箭頭函數繫結了父級作用域的上下文,所以 this 將不會按照期望指向 Vue 範例,this.a 將是 undefined。

長話短說,官方的意思是:在Vue範例中,methods中如果用的是正常函數,那麼它的this就指向Vue範例;如果是箭頭函數,this就指向window物件;

總結:

Vue methods 中不應該箭頭函數定義methods函數,因為箭頭函數繫結了父級作用域上下文,所以 this 列印出的結果是Window 物件;不使用箭頭函數的情況下,this 實際上是指向了一個 Proxy 物件。

原因是vue 內部實際上對methods屬性中的方法進行了遍歷,將對應的方法通過bind繫結了this,使得this指向範例vm

2.Vue中生命週期勾點和自定義方法中的this指向當前的 Vue 範例

所有的生命週期勾點自動繫結 this 上下文到範例中,因此你可以存取資料,對 property 和方法進行運算。這意味著你不能使用箭頭函數來定義一個生命週期方法 (例如 created: () => this.checkTodos())。這是因為箭頭函數繫結了父上下文,因此 this 與你期待的 Vue 範例不同,this.checkTodos 的行為未定義。

3. Vue 中回撥函數中的 this:

  • 若回撥函數為匿名函數,非嚴格模式下指向 window,嚴格模式下為 undefined。
  • 若回撥函數為自定義方法,則 this 指向 Vue 範例。
  • 若回撥函數為 箭頭函數,則 this 指向 Vue 範例。

4. Vue 中 addEventListener 中的 this

通常,事件監聽函數中的 this 都指向繫結事件的那個元素, 但是在 Vue 中,監聽函數中的 this 也指向 Vue 範例

5.在data裡定義Object型別的變數時的this

在data裡定義Object型別的變數時,會發現Object中存取不到vue的this屬性,例如:

export default {
  data(){
    return {
      a: "123",
      b: {
        c: this.a
      }
    };
  },
  created() {
    console.log("b: ", this.b.c); // undefined
  }
}

想在b中存取this.a的資料,直接存取會返回undefined,因為這時c中的this指向的是b。這種情況可以用到Object的get屬性進行屬性定義,例如:

export default {
  data(){
    return {
      a: "123",
      b: {
        _target: () => this,
        get target() {
          return this._target();
        },
 
        get c() {
          return this.target.a;
        },
      },
    };
  },
  created() {
    console.log("b: ", this.b.c); // 123
  }
}

此處將this對映到了Object變數內部,然後通過get的形式定義屬性並獲取,這樣就解決問題啦。 

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。 


IT145.com E-mail:sddin#qq.com