this 指向詳細解析(箭頭函數)


本文地址:http://www.cnblogs.com/dongcanliang/p/7054176.html

前言

this 指向問題是入坑前端必須了解知識點,現在迎來了ES6時代,因為箭頭函數的出現,所以感覺有必要對 this 問題梳理一下,遂有此文

在非箭頭函數下, this 指向調用其所在函數的對象,而且是離誰近就是指向誰(此對於常規對象,原型鏈, getter & setter等都適用);構造函數下,this與被創建的新對象綁定;DOM事件,this指向觸發事件的元素;內聯事件分兩種情況,bind綁定, call & apply 方法等, 容以下一步一步討論。箭頭函數也會穿插其中進行討論。

全局環境下

在全局環境下,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, 歡迎交流學習

參考文獻

this - JavaScript | MDN
箭頭函數 - JavaScript | MDN


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM