1. 程式人生 > >箭頭函式及this指向問題

箭頭函式及this指向問題





 

全域性環境下

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

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

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

函式上下文呼叫

函式直接呼叫

普通函式內部的this分兩種情況,嚴格模式和非嚴格模式。

非嚴格模式下,this 預設指向全域性物件window

function f1(){
  return this; } f1() === window; // true

而嚴格模式下, this為undefined

function f2(){
 "use strict"; // 這裡是嚴格模式 return this; } f2() === undefined; // true

物件中的this

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

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

原型鏈中this

原型鏈中的方法的this仍然指向呼叫它的物件,與以上討論一致;看個例子,

var o = {
  f : function(){ return this.a + this.b; } }; var p = Object.create(o); p.a = 1; p.b = 4; console.log(p.f()); // 5

可以看出, 在p中沒有屬性f,當執行p.f()時,會查詢p的原型鏈,找到 f 函式並執行,但這與函式內部this指向物件 p 沒有任何關係,只需記住誰呼叫指向誰。

以上對於函式作為getter & setter 呼叫時同樣適用。

建構函式中this

建構函式中的this與被建立的新物件繫結。

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

function C(){
  this.a = 37; } var o = new C(); console.log(o.a); // logs 37 function C2(){ this.a = 37; return {a:38}; } var b = new C2(); console.log(b.a); // logs 38

以上兩個例子內部的this都指向物件o, 看到這裡的你不妨在控制檯執行下以上程式碼,看看物件 o 和 b ,這些是屬於建構函式的內容了,此處不多做介紹。(C2函式中的this.a = 37 對整個過程完全沒有影響的, 可以被忽略的)。

call & apply

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

例子:

function add(c, d){
  return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34 function tt() { console.log(this); } // 返回物件見下圖(圖1) tt.call(5); // Number {[[PrimitiveValue]]: 5} tt.call('asd'); // String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}

bind 方法

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

function f(){
  return this.a; } var g = f.bind({a:"azerty"}); console.log(g()); // azerty var o = {a:37, f:f, g:g}; console.log(o.f(), o.g()); // 37, azerty

DOM 事件處理函式中的 this & 內聯事件中的 this

DOM事件處理函式

  1. 當函式被當做監聽事件處理函式時, 其 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); } 

以上程式碼建議在網頁中執行以下,看下效果。

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

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

頁面的程式碼塊

在瀏覽器內顯示三個按鈕

依次點選上邊的三個按鈕後在控制檯的輸出結果,

建議自己操作一遍以便於更好的理解。

setTimeout & setInterval

對於延時函式內部的回撥函式的this指向全域性物件window(當然我們可以通過bind方法改變其內部函式的this指向)
看下邊程式碼及截圖

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

箭頭函式中的 this

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

  1. 所以 call() / apply() / bind() 方法對於箭頭函式來說只是傳入引數,對它的 this 毫無影響。
  2. 考慮到 this 是詞法層面上的,嚴格模式中與 this 相關的規則都將被忽略。(可以忽略是否在嚴格模式下的影響)

因為箭頭函式可以捕獲其所在上下文的this值 所以

function Person() {  
    this.age = 0; setInterval(() => { // 回撥裡面的 `this` 變數就指向了期望的那個物件了 this.age++; }, 3000); } var p = new Person();

以上程式碼可以得到我們所以希望的值,下圖可以看到,在setTimeout中的this指向了建構函式新生成的物件,而普通函式指向了全域性window物件

var adder = {
  base : 1,
    
  add : function(a) { var f = v => v + this.base; return f(a); }, addThruCall: function inFun(a) { var f = v => v + this.base; var b = { base : 2 }; return f.call(b, a); } }; console.log(adder.add(1)); // 輸出 2 console.log(adder.addThruCall(1)); // 仍然輸出 2(而不是3,其內部的this並沒有因為call() 而改變,其this值仍然為函式inFun的this值,指向物件adder

bind() & apply() 讀者可以自行測試

對於是否嚴格模式示例程式碼(可以複製進控制檯進行驗證)

var f = () => {'use strict'; return this}; var p = () => { return this}; console.log(1,f() === window); console.log(2,f() === p()); //1 true //2 true

以上的箭頭函式都是在方法內部,總之都是以非方法的方式使用,如果將箭頭函式當做一個方法使用會怎樣呢?
上例子

var obj = {
  i: 10,
  b: () => console.log(this.i, this), c: function() { console.log( this.i, this) } } obj.b(); // undefined window{...} obj.c(); // 10 Object {...}

可以看到,作為方法的箭頭函式this指向全域性window物件,而普通函式則指向呼叫它的物件

以上為個人學習整理內容, 文中例子參考MDN, 歡迎交流學習