看完就能搞懂的this指向及箭頭函數的講解~

本文章預計耗時15 - 20分鐘,包含執行代碼驗證的時間。很是建議仔細閱讀並手動執行代碼,以驗證不一樣操做的結果。整理及驗證不易,若是你收穫了新知識,請不吝惜的點個贊吧。若是你讀完後對this指向還有疑問,歡迎在底部留言~javascript

與其餘語言相比,函數的 this 關鍵字在 JavaScript 中的表現略有不一樣,此外,在嚴格模式和非嚴格模式之間也會有一些差異。前端

  • 函數的調用方式決定了this的值。
  • this不能在執行期間被賦值,而且在每次函數被調用時this的值也可能會不一樣。
  • ES5引入了bind方法來設置函數的this值,而不用考慮函數如何被調用的,
  • ES2015 引入了支持this詞法解析的箭頭函數(它在閉合的執行環境內設置this的值)。

this指當前執行代碼的環境對象,在非嚴格模式下,老是指向一個對象,在嚴格模式下能夠是任意值java

全局環境

不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this 都指向全局對象。node

// 在瀏覽器中, window 對象同時也是全局對象:
console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "MDN";
console.log(window.b)  // "MDN"
console.log(b)         // "MDN"
複製代碼

能夠直接使用globalThis 來獲取不一樣環境下的全局 this 對象(也就是全局對象自身)瀏覽器

函數環境

在函數內部,this的值取決於函數被調用的方式。app

簡單調用

在非嚴格模式下,直接在全局做用域下調用函數, this 的值不是由該調用設置的,因此 this 的值默認指向全局對象。異步

若是函數名前沒有加任何東西,那麼默認爲簡單調用,函數

function f1(){
  return this;
}
//在瀏覽器中:
f1() === window;   //在瀏覽器中,全局對象是window

//在Node中:
f1() === global;   
複製代碼

在嚴格模式下,this將保持他進入執行環境時的值,因此下面的this將會默認爲undefinedpost

function f2(){
 "use strict"; // 這裏是嚴格模式
  return this;
}

f2() === undefined; // true
複製代碼

嚴格模式下,若是 this 沒有被執行環境(execution context)定義,那它將保持爲 undefinedui

使用 call 或者apply 方法能夠將其主體中使用的 this 綁定到某個對象。

// 將一個對象做爲call和apply的第一個參數,this會被綁定到這個對象。
var obj = {a: 'Custom'};

// 這個屬性是在global對象定義的。
var a = 'Global';

function whatsThis(arg) {
  return this.a;  // this的值取決於函數的調用方式
}

whatsThis();          // 'Global'
whatsThis.call(obj);  // 'Custom'
whatsThis.apply(obj); // 'Custom'
複製代碼

若是傳遞給 this 的值不是一個對象,JavaScript 會嘗試使用內部 ToObject 操做將其轉換爲對象。所以,若是傳遞的值是一個原始值好比 7'foo',那麼就會使用相關構造函數將它轉換爲對象,因此原始值 7 會被轉換爲對象,像 new Number(7) 這樣,而字符串 'foo' 轉化成 new String('foo')

bind方法

ECMAScript 5 引入了 Function.prototype.bind()。調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind的第一個參數,不管這個函數是如何被調用的。

function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.a, o.f(), o.g(), o.h()); // 37, 37, azerty, azerty
複製代碼

做爲對象的方法

當普通函數做爲對象裏的方法被調用時,它們的 this 是調用該函數的對象。

下面的例子中,當 o.f()被調用時,函數內的this將綁定到o對象。

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // 37
複製代碼

請注意,函數內部的this指向調用函數的對象,跟函數所在位置無關。以下面例子:

// 接上面代碼, 建議在瀏覽器控制檯運行下面代碼,node環境下你只會一臉懵逼,好比我

var prop = 10; 
const func = o.f;

// 在全局環境下直接調用函數
console.log(func()); // 10 

// 定義新的對象
const o1 = {
  prop: 15
}

// 將o1中的func指向o中的函數f
o1.func = o.f;

// 經過o1調用, 函數內this指向o1
console.log(o1.func()); // 15
複製代碼

原型鏈中的 this

對於在對象原型鏈上某處定義的方法,一樣的概念也適用。若是該方法存在於一個對象的原型鏈上,那麼this指向的是調用這個方法的對象,就像該方法在對象上同樣。

做爲構造函數

當一個函數用做構造函數時(使用new關鍵字),它的this被綁定到正在構造的新對象。

function C(){
  this.a = 37;
}

var o = new C();
console.log(o.a); // logs 37
複製代碼

做爲一個DOM事件處理函數

當函數被用做事件處理函數時,它的this指向觸發事件的元素(一些瀏覽器在使用非addEventListener的函數動態添加監聽函數時不遵照這個約定)。

// 被調用時,將關聯的元素變成藍色
function bluify(e){
  console.log(this === e.currentTarget); // 老是 true

  // 當 currentTarget 和 target 是同一個對象時爲 true
  console.log(this === e.target);        
  this.style.backgroundColor = '#A5D9F3';
}

// 獲取文檔中的全部元素的列表
var elements = document.getElementsByTagName('*');

// 將bluify做爲元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
  elements[i].addEventListener('click', bluify, false);
}
複製代碼

嵌套函數

嵌套函數內部this與調用函數所在環境的this無關

function foo() {
  console.log(this) 
  function boo() {
    console.log(this) 
  }
  boo()
}

// 直接調用
foo() // window window

// new 對象
const f = new foo() // foo實例 window
複製代碼

這裏我我的的理解是:函數內部調用的函數,由於沒有顯式指定調用對象,因此內部this指向全局。

定時器與微任務

微任務中的簡單調用的函數this指向window嚴格下指向undefined,而定時器中的回調函數無論在嚴格仍是非嚴格環境下this永遠指向window

異步任務中簡單調用的函數都是進入隊列,最後由全局環境調用

箭頭函數

箭頭函數中,this與封閉詞法環境的this保持一致。

全局環境中

在全局代碼中,它將被設置爲全局對象:

var globalObject = this;
var foo = () => this;

// 全局代碼中調用
console.log(foo() === globalObject); // true

// 做爲對象的一個方法調用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// 使用call來設定this, 操做無效
console.log(foo.call(obj) === globalObject); // true

// 使用bind來設定this,操做無效
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
複製代碼

注意:將this傳遞給callbind、或者apply來調用箭頭函數操做是無效的。

箭頭函數不會建立本身的this,它只會從本身的做用域鏈的上一層繼承this。

下面代碼請在控制檯中執行

箭頭函數做爲對象方法

// 仍是這個對象
var o = {
  prop: 37,
  f: function() {
    return this.prop;
  },
  g: () => {
    return this.prop;
  }
};

// 分別調用
console.log(o.f()); // 37,this指向對象 o
console.log(o.g()); // undefined,this指向全局

// 在全局中聲明 prop
var prop = 10;

// 分別調用
console.log(o.f()); // 37
console.log(o.g()); // 10
複製代碼

你能夠參考一下Object.defineProperty()的示例,大概就能夠理解爲何對象方法裏的箭頭函數內部this指向全局了:

'use strict';
var obj = {
  a: 10
};

Object.defineProperty(obj, "b", {
  get: () => {
    console.log(this.a, typeof this.a, this);
    return this.a+10; 
   // 表明全局對象 'Window', 所以 'this.a' 返回 'undefined'
  }
});
複製代碼

函數內部執行箭頭函數

在下面的代碼中,傳遞給setInterval的函數內的this與封閉函數中的this值相同:

function Person(){
  this.age = 0;

  setTimeout(() => {
    this.age++; // |this| 正確地指向 p 實例
  }, 1000);
}

var p = new Person();
p.age; // 1
複製代碼

換一種方式調用:

// 聲明全局變量
var prop = 10;

// 聲明一個對象
const o1 = {
  prop: 15
}


function func1() {
  const prop = 11;
  const te = () => {
    return this.prop;
  }
  return te();
}

console.log(func1()) // 10
console.log(func1.call(o1)) // 15
複製代碼

從上面的代碼能夠看出,在函數內部定義的箭頭函數,其內部使用的this繼承自外部函數,此時,箭頭函數的this就不是固定的了,而是會在外部函數func1執行時肯定。

再換一種方式調用:

// 聲明全局變量
var prop = 10;

// 聲明一個對象
const o1 = {
  prop: 15
}

function func1() {
  const prop = 11;
  const te = () => {
    return this.prop;
  }
  return te;
}

// func1在全局環境下調用
o1.func = func1();
console.log(o1.func()) // 10,箭頭函數的this指向全局對象

// func1在o1方法下調用
o1.func1 = func1;
console.log(o1.func1()()) // 15, 箭頭函數的this指向o1
複製代碼

結論:函數內部的箭頭函數的this繼承外部函數的this,所以不是固定的,而是會在外部函數func1執行時肯定。

請注意,當使用this.te = () => { return this.prop },te做爲對象的屬性而不是函數的局部變量,所以內部this再也不繼承自函數,而是指向全局,具體參考上面箭頭函數做爲對象方法一節。

最後,來講明一下怎麼使對象內部的箭頭函數方法的this再也不固定,沒錯,就是在函數內部定義對象,對象內部聲明方法。

// 聲明全局變量
var prop = 10;

// 聲明一個對象
const o1 = {
  prop: 15
}

function test(){
  const prop = 20;
  const o2 = {
    prop: 30,
    f: () => {
      return this.prop;
    }
  }
  return o2.f();
}

// 在全局環境下調用
console.log(test()) // 10

// 使用o1調用
console.log(test.call(o1)) // 15
複製代碼

如今你明白this會指向哪裏了麼?

相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)

若是你收穫了新知識,請給做者點個贊吧~

參考文檔:

this

相關文章
相關標籤/搜索