JavaScript 中 this 指向詳解

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰! javascript

使用 JavaScript 開發的時候,不少開發者多多少少會被 this 的指向搞蒙圈,可是實際上,關於 this 的指向,記住最核心的一句話:哪一個對象調用函數,函數裏面的 this 指向哪一個對象前端

this的幾種模式:

  1. 方法調用模式下,this 老是指向調用它所在方法的對象,this 的指向與所在方法的調用位置有關,而與方法的聲明位置無關(箭頭函數特殊);
  2. 函數調用下,this 指向 window ,調用方法沒有明確對象的時候,this 指向 window,如 setTimeout、匿名函數等;
  3. 構造函數調用模式下,this 指向被構造的對象;
  4. apply,call,bind 調用模式下,this 指向第一個參數;
  5. 箭頭函數,在聲明的時候綁定this,而非取決於調用位置;
  6. 嚴格模式下,若是 this 沒有被執行環境(execution context)定義,那 this是 爲undefined;

下面咱們針對這幾種狀況,舉例並說明原理:java

1. 方法調用模式

// 聲明位置
var test = function(){
  console.log(this.x)
} 

var x = "2";

var obj = {
  x:"1",
  fn:test,
}

// 調用位置
obj.fn(); // 1

test(); // 2
複製代碼

以上代碼,能夠看到,this 指向調用它所在方法的對象,test 方法在 obj 對象下,因此 this 指向 obj,test 在window 對象下,因此 this 指向 window。也能夠看出來:this和聲明位置無關,和調用位置有關。後端

可是下面這個狀況得注意數組

let obj1={
  a:222
};
let obj2={
  a:111,
  fn:function(){
    console.log(this.a);
  }
}
obj1.fn = obj2.fn;
obj1.fn(); // 222
複製代碼

這個不難理解,雖然 obj1.fn 是從 obj2.fn 賦值而來,可是調用函數的是obj1,因此 this 指向 obj1。markdown

2. 函數調用模式

var a = 1;
function fn1(){
  console.log(this.a); // 1
}
fn1();

window.b = 2;
function fn2(){
  console.log(this.b); // 2
}
fn2();
//能夠理解爲 window.fn();
複製代碼

匿名函數,setTimeout:app

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

setTimeout(() => {
  console.log(this); // window
}, 0);

setTimeout(function(){
  console.log(this); // window
}, 0);
複製代碼

3. 構造函數模式

var flag = undefined; 

function Fn(){
  flag = this;
}    

var obj = new Fn();

console.log(flag === obj); // true
複製代碼

這個 this 指向 obj,內部原理仍是用 apply 把 this 指向obj的,回憶下JavaScript中 new 一個對象過程詳解函數

4. call、apply、bind

call 和 apply 的做用,徹底同樣,惟一的區別:參數; call 接收的參數不固定,第一個參數是函數體內 this 的指向,第二個參數如下是依次傳入的參數。 apply接收兩個參數,第一個參數也是函數體內 this 的指向。第二個參數是一個集合對象(數組或者類數組)oop

var obj = {
  name:'111',
  getName:function(){
    console.log(this.name)
  }
};

var otherObj = {
  name:'222',
};

var name = '333';
        
obj.getName();               // 111
obj.getName.call();          // 333
obj.getName.call(otherObj);  // 222
obj.getName.apply();         // 333
obj.getName.apply(otherObj); // 222
obj.getName.bind(this)();    // 333
obj.getName.bind(otherObj)();// 222
複製代碼

5. 箭頭函數

關於 ES6 中的箭頭函數,官方的解釋是: 箭頭函數裏面的 this 是上下文( context ), 外部做用域的 this 就是箭頭函數內的 this。post

判斷箭頭函數的 this:

技巧:它的外層沒有函數,this 是 window;外層有函數,看外層函數的 this 是誰,它的 this 就是誰。

外層函數多是常規函數多是箭頭函數,判斷外層的 this 要根據函數種類用不一樣方法:

外層函數是常規函數就看外層函數是被誰調用的;

外層是箭頭函數就根據剛纔說的技巧來判斷;

let obj={
  a:222,
  fn:function(){    
    setTimeout(()=>{console.log(this.a)});
  }
};
obj.fn(); // 222
複製代碼
var name = 'window'; 
var A = {
  name: 'A',
  sayHello: () => {
    console.log(this.name)
  }
}

A.sayHello(); // 輸出的是window,根據剛纔講的規則就能夠判斷

// 那如何改形成永遠綁定A呢:

var name = 'window'; 
var A = {
  name: 'A',
  sayHello: function(){
    var s = () => console.log(this.name)
    return s//返回箭頭函數s
  }
}

var sayHello = A.sayHello();
sayHello();// 輸出A 
複製代碼
  • call() 、apply() 、 bind() 方法對於箭頭函數來講只是傳入參數,對它的 this 毫無影響;
  • 考慮到 this 是詞法層面上的,嚴格模式中與 this 相關的規則都將被忽略(忽略是否在嚴格模式下的影響);
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

var obj = {foo: foo};
console.log(foo.call(obj) === globalObject); // true

foo = foo.bind(obj);
console.log(foo() === globalObject); // true
複製代碼

6. 嚴格模式

非嚴格模式下,this 默認指向全局對象 window;

// 非嚴格模式
function f1(){
  return this;
}
console.log(f1() === window); // true
複製代碼

嚴格模式下, this 爲undefined;

// 嚴格模式
"use strict";
var fn2 = function(){
  return this
}    
console.log(fn2() == undefined); // true
複製代碼
相關文章
相關標籤/搜索