this

個人博客地址 → this | The story of Captain,轉載請註明出處。

問:this 是什麼?javascript

答:thiscall 方法的第一個參數,call 的第一個參數就是 thishtml

完。java

就這麼簡單麼?是的。jquery

爲何這樣說?由於全部的函數/方法調用的時候均可以 轉換call 形式,call 的第一個參數顯式的指明瞭函數該次執行時候的上下文。閉包

今天咱們深刻探討一下如何肯定 thisapp

如何肯定 this

this 由函數的上下文肯定。函數

如何肯定「上下文」 ?

上下文分爲 全局上下文(Global Context) 以及 函數上下文(Function Context)優化

全局上下文

在全局中,this 一概指向 全局對象 window。例如:this

console.log(this === window); //; true

函數上下文

在函數中,上下文由函數被調用的方式決定。spa

  • 簡單調用

    以 「函數名( )」 形式調用的函數即爲簡單調用,簡單調用時上下文爲全局上下文,所以 this === window

    舉例一:

    function foo () {
      console.log(this === window);
    }
    foo(); // true

    舉例二:

    function fn1 () {
      function fn2 () {
        console.log(this === window);
      }
      fn2();
    }
    fn1(); // true,由於 fn2 爲簡單調用

    舉例三:

    let obj = {
      fn1: function () {
        console.log(this === window);
      }
    };
    let fn2 = obj.fn1; 
    fn2(); // true

    第三個例子中,爲何 fn2() 執行結果爲 true ?由於執行了 let fn2 = obj.fn1 以後 fn2 爲:

    fn2 = function () {
      console.log(this);
    }

    再執行 fn2() 時,爲簡單調用,所以 this === window

  • 方法調用

    當函數做爲一個對象的方法被調用時,this 指向該對象。

    舉例一:

    let obj = {
      fn1: function () {
        console.log(this === obj);
      }
    };
    obj.fn1(); // true

    obj.fn1() 形式調用 fn1 時,是以方法形式調用的,this 指向該函數所屬的對象,即 obj

    舉例二:

    let obj = {
      fn1: {
        fn2:function () {
          console.log(this === obj.fn1);
        }
      }
    };
    obj.fn1.fn2(); // true

    obj.fn1.fn2() 形式調用 fn2 時,是以方法形式調用的,this 指向該函數所屬的對象,即 obj.fn1,不少人常誤覺得此處的 this 指向 obj,這是錯誤的。

    舉例三:

    let obj = {
      fn1: function () {
        return function () {
          console.log(this === window);
        }
      }
    };
    let fn2 = obj.fn1();
    fn2(); // true

    爲何 fn2() 的執行結果爲 true ?由於執行了 let fn2 = obj.fn1() 以後 fn2 爲:

    fn2 = function () {
      console.log(this === window);
    }

    再執行 fn2() 時,爲簡單調用,所以 this === window 。若是想要將 fn2 中的 this 指向 obj,可將指向 objthis 保存在中間變量,改動以下所示:

    let obj = {
      fn1: function () {
        let that = this;
        return function () {
          console.log(that === obj);
        }
      }
    };
    let fn2 = obj.fn1();
    fn2(); // true

    利用 let that = thisfn1 中的 this 保存在 that 變量中,而後 fn2() 的結果即爲 true,固然這其中涉及到了 閉包(closure) 的知識。

特殊的 this

如下狀況中的 this 須要進行特殊記憶。

箭頭函數

箭頭函數(arrow function,=>),箭頭函數爲 ES6 中引入的新的函數表示法,不一樣之處在於,箭頭函數中沒有 this,箭頭函數中的 this 爲其執行上下文中的 this,如何理解?舉例說明。

舉例一:

() => console.log(this === window); // true

其執行上下文爲全局上下文,this 指向 window

舉例二:

function foo () {
  return () => console.log(this === window);
};
foo()(); // true

和方法調用中的舉例三相似。

舉例三:

let obj = {
  fn1: () => console.log(this === window);
};
obj.fn1(); // true

爲何是 true ?方法調用中的舉例一中的 this 不是 obj 嗎?沒錯,箭頭函數 fn1 中是沒有本身的 this 的,所以 this 不指向 obj ,繼續向上找 obj 的上一級,直到找到有 this 的上下文爲止,obj 處在全局上下文中, 全局上下文中有 this,所以箭頭函數中的 this 爲全局上下文中的 this,即 指向 window

舉例四:

let obj = {
  fn1: function () {
    return () => console.log(this === obj);
  }
};
let fn2 = obj.fn1();
fn2(); // true

此處又和方法調用的舉例三不一樣,由於箭頭函數中是沒有本身的 this 的,箭頭函數中的 this 爲其上一級的 this ,所以,箭頭函數中的 this 爲其上一級,即 fn1 中的 thisfn1 中的 this 指向 obj,因此箭頭函數中的 this 指向 obj。根據箭頭函數的特性:箭頭函數中的 this 保留了其上一級的 this 指向,那麼方法調用舉例三的改動能夠優化爲本例所示,用一個箭頭函數便可解決,省去了中間變量。

構造函數

當一個函數做爲構造函數使用時,構造函數的 this 指向由該構造函數 new 出來的對象。舉例說明:

function CreateNewPerson (name,gender,age) {
  this.name = name;
  this.gender = gender;
  this.age = age;
}
let me = new CreateNewPerson('daijt','male',18);
console.log(me.name); // 'daijt'
console.log(me.gender); // 'male'
console.log(me.age); // 18

執行 let me = new CreateNewPerson('daijt','male',18) 時,構造函數中的 this 直接指向由其 new 出來對象對象 me ,所以執行完該句後 me 的結構以下:

me = {
  name: 'daijt',
  gender: 'male',
  age: 18
}

原型鏈

舉例一:

let name = new String('daijt');
name.toUpperCase(); // DAIJT

根據上文構造函數中的 this,執行 let name = new String('daijt') 時,String 構造函數中的 this 指向了 name,而 name__proto__ 屬性,該屬性指向全部 string 類的共有屬性或者方法,而這些共有的屬性和方法都保存在 String.prototype 中,即:

name.__proto__ === String.prototype; // true

所以 name 是有 toUpperCase 方法的(原型鏈繼承而來),調用 toUpperCase 時,toUpperCase 中的 this 指向 name,所以 name.toUpperCase() 的結果爲 DAIJT

舉例二:

let name = 'daijt';
name.toUpperCase.(); // DAIJT

爲什麼沒有經過 new 出來的對象也具備 toUpperCase 方法呢?由於在執行 let name = 'daijt' 的過程當中,JS 有一個臨時轉化的過程,例如:

let name = (function (string) {
  return new String(string);
})('daijt');

所以,name 也繼承了 string 類共有的屬性和方法,這也算是 JS 的一個語法糖吧。 固然,這涉及到了其餘的知識。

DOM EventHandle

舉例:

let buttons = document.querySelector('button');
buttons.addEventListener('click', function (event) {
  console.log(this === event.currentTarget); // true
});

使用 addEventListener 綁定 DOM 時,監聽函數中的 this 指向觸發事件的 currentTargetcurrentTarget 表示被綁定了監聽函數的 DOM 元素。

注意:若是是經過冒泡觸發監聽函數的話, event.target 不必定等於 event.currentTarget

jQuery EventHandle

HTML:

<ul id="father-ul">
  <li class='father-li'>father-ul的第1個li</li>
  <li class='father-li'>father-ul的第2個li
    <ul>
      <li>son-ul的第1個li</li>
      <li>son-ul的第2個li</li>
      <li>son-ul的第3個li</li>
    </ul>
  </li>
  <li class='father-li'>father-ul的第3個li</li>
</ul>

JavaSctipt:

$('#father-ul').on('click', '.father-li', function (event) {
  console.log(event.target); 
  console.log(event.currentTarget);
  console.log(this === currentTarget);
});

當點擊 <li class='father-li'>father-ul的第1個li</li> 時,控制檯打印出:

<li class='father-li'>father-ul的第1個li</li>
<li class='father-li'>father-ul的第1個li</li>
true

當點擊 <li>son-ul的第2個li</li> 時,控制檯打印出:

<li>son-ul的第2個li</li>
<li class='father-li'>father-ul的第2個li
    <ul>
      <li>son-ul的第1個li</li>
      <li>son-ul的第2個li</li>
      <li>son-ul的第3個li</li>
    </ul>
</li>
true

所以能夠得出結論:jQuery EventHandle 中的 this 指的是被代理事件監聽的 DOM 元素,也就是匹配全部選擇器的 DOM 元素,即 .father-li ,具體解釋可參照 jQuery 文檔

### 如何改變 this

以上所述的 this 都爲肯定的 this,那麼如何本身設置 this,改變 this 的指向呢?或者說如何動態改變上下文呢?ES5 爲咱們提供了三個全局方法:call()apply()bind()。三個方法均可以動態的改變上下文,即 this 的指向,三者的區別能夠參照 MDN,以 call() 爲例進行說明。

var name = '全局上下文';
let me = {
  name: 'daijt',
  gender: 'male'.
  age: 23,
};
let myGirlFriend = {
  name: 'xiaofang',
  gender: 'female',
  age: 18
};
function printName() {
  console.log(this.name);
}
printName(); // window
printName.call(me); // daijt
printName.call(myGirlFriend); // xiaofang
  • 執行 printName() 時:

    簡單調用,所以其內部的 this 指向 全局上下文,所以 this === window ,而使用 var 關鍵字在全局聲明的變量會做爲 window 對象的屬性,所以 this.name === window.name === 全局上下文

  • 執行 printName.call(me) 時:

    由於 call() 的第一個參數爲 thisArg ,所以使用 call() 顯式的指明瞭 printName 函數本次執行的上下文,即 me,因 this 指向上下文,因此 this === methis.name === me.name === daijt

  • 執行 printName.call(myGirlFriend) 與執行 printName.call(me) 同理。

技巧

回到本文開頭,全部的函數/方法調用的時候均可以 轉換call 形式,call 的第一個參數顯式的指明瞭函數該次執行時候的上下文,這就是判斷 this 指向的技巧,以代碼爲例進行演示:

舉例一:

function foo () {
  console.log(this);
}
foo(); // window
foo.call(); // window

// non-strict mode
foo.call(undefined); // window
// strict mode
foo.call(undefined); // undefined
  • foo() 爲簡單調用,所以 this === window
  • foo.call() 中,call() 的第一個參數未指明,那麼 this === window ,在全局上下文中,非嚴格模式 下,undefined 即爲 window嚴格模式 下,undefined 不能指代 window ,因此嚴格模式下 this === undefined

舉例二:

let obj = {
  fn1: function () {
    console.log(this === obj);
  }
};
obj.fn1(); // true
obj.fn1.call(obj); // true

舉例三:

let obj = {
  fn1: {
    fn2:function () {
      console.log(this === obj.fn1);
    }
  }
};
obj.fn1.fn2(); // true
obj.fn1.fn2.call(obj.fn1); // true

舉例四:

let obj = {
  fn1: function () {
    return function () {
      console.log(this === window);
    }
  }
};
let fn2 = obj.fn1();
fn2(); // true
fn2.call(); // true
obj.fn1.call(obj).call(undefined); // true

以上三個例子中,如何判斷傳給 call()this 呢?以舉例四的最後一句代碼爲例進行分析:

call.png

經過這張 call() 的圖解,this 應該徹底掌握了,因此將函數的調用改寫爲 call() 形式是最直接明瞭判斷 this 的方法。

看到這裏,你搞懂 this 了嗎?

參考連接:

更多精彩內容,請點擊個人博客 → The story of Captain
相關文章
相關標籤/搜索