JS 的 this 指來指去到底指向哪?(call, apply, bind 改變 this 指向)

前言:
this指向問題一直是js中最容易犯的錯誤之一。
今天就寫下這篇博文,談一下我對this的理解。
若是你們以爲有幫助,請點個贊關注我吧。
若是有不對的地方,歡迎你們指正!面試

先來看個思惟導圖吧: 數組

1、ES5中funciton的this指向bash

  1. 函數經過new 構造函數建立出來的實例對象this指向的是該實例對象。
function Person() {
    console.log(this, '1') //Person
    this.say = function (){
      console.log( this, '2'); //Person
    }
  }
  // 在構造函數中,this指向的是new出來的實例,因此兩處this都指向Person
  var per = new Person()
  per.say()
複製代碼


2. 由於構造函數自己是沒法訪問其自身的值,實例化對象能夠。 這裏爲了比較,作了如下的寫法:微信

function Person() {
      console.log(this, '1') //Window
      this.say = function (){
        console.log( this, '2'); //Window
        this.eat = function () {
          console.log(this, '3'); //Window
        }
        eat()
      }
      say()
    }
    Person()
複製代碼

解析:用函數名()調用時,不管嵌套多少層,this默認指向的也是window。閉包


3. 在對象中建立的函數this指向是:this 永遠指向最後調用它的那個對象!this 永遠指向最後調用它的那個對象!this 永遠指向最後調用它的那個對象!(重要的話說3次)牢記這條就不會出錯。app

//eg1:  
var a = 2
var obj = {
  a: 1,
  b: function() {
    console.log(this.a) //1  這裏是obj調用的,因此this指向obj
  }
}
obj.b()
複製代碼

假如咱們假如了setTimeout呢,狀況會是什麼樣的呢?函數

//eg2:  
var a = 2
var obj = {
  a: 1,
  b: function() {
    window.setTimeout(function() {
      console.log(this.a) //2  
    })
    console.log(this.a) //1 
  }
}
obj.b()
複製代碼

解析:setTimeout的執行環境是Window,因此會把this指向改變到Window上。ui


4. 那假如在對象中嵌套多層呢?狀況會是怎麼樣的呢?this

const obj = {
      a: function() { console.log(this) },
      b: {
        c: function() {console.log(this)}
      }
    }
    obj.a()          // obj, obj調用的a()方法
    obj.b.c()        //obj.b, obj.b調用的a()方法
複製代碼

解析:仍是那句話:this 永遠指向最後調用它的那個對象!,這下記住了吧。spa


5. 還有一個狀況就是閉包中的this指向,這裏也講解一下。 (面試的時候可能會出這種變形題。)

//eg1:
var name = '張' 
function Person() {
  this.name = '柳';
  let say = function (){
    console.log( this.name + ' do Something');//張 do Something
  }
  say()
}
var per = new Person()
複製代碼

咱們能夠把這道題換種寫法:

//eg2: 
var name = '張' 
function Person() {
  this.name = '柳';
  return function (){  //本質都是匿名函數的自執行
    console.log( this.name + ' do Something');//張 do Something
  }
}
var per = new Person()
per()
複製代碼

解析:看到這就明白了,其實這是一道閉包題。閉包函數具備匿名函數自執行的特性,默認this指向是掛在Window下的。



2、 ES6箭頭函數中的this

《深刻淺出ES6》(65頁)中關於箭頭函數this的解釋:
    箭頭函數中沒有this綁定,必須經過查找做用域鏈來決定其值。
    若是箭頭函數被非箭頭函數包圍,那麼this綁定的是最近一層非箭頭函數的this;
    不然,this的值會被設置爲undefined。
複製代碼

個人理解:

若是箭頭函數被非箭頭函數包圍,那麼this綁定的是最近一層非箭頭函數的this,我理解的這句話是說,若是箭頭函數的父級是function,那麼箭頭函數的this指向會與funciton中的this指向保持一致。
若是上面的聽不太明白,那麼能夠參考你們的理解方式:爲箭頭函數的this是在定義的時候綁定的。那麼問題來了:

1.何爲定義時綁定?

//eg1:
var x=11;
var obj={
  x:22,
  say:function(){
    console.log(this.x)
  }
}
obj.say();
//console.log輸出的是2
複製代碼

解析:通常的定義函數跟咱們的理解是同樣的,運行的時候決定this的指向,咱們能夠知道當運行obj.say()時候,this指向的是obj這個對象。

//eg2:
var x=11;
var obj={
 x:22,
 say:()=>{
   console.log(this.x);
 }
}
obj.say();
//輸出的值爲11

複製代碼

解析:
這樣就很是奇怪了若是按照以前function定義應該輸出的是22,而此時輸出了11,那麼如何解釋ES6中箭頭函數中的this是定義時的綁定呢?

所謂的定義時候綁定,this綁定的是最近一層非箭頭函數的this;由於箭頭函數是在obj中運行的,obj的執行環境就是window,所以這裏的this.x實際上表示的是window.x,所以輸出的是11。


2. 理解了對象中的this定義後,還有幾點要提一下:
對象中箭頭函數的this指向的是window。且沒法改變其指向。
緣由:函數的this能夠用call方法來手動指定,是爲了減小this的複雜性,箭頭函數沒法用call等方法來指定this。

//eg3:
  var a = 2
  const obj = {
    a: 1,
    b: () => {
      console.log(this.a)
    }
  }
  obj.b()//2 默認綁定外層this
  obj.b.call(obj.a)//2 不能用call方法修改裏面的this
複製代碼
  1. 假如在window.setTimeout中使用箭頭函數呢?
//eg4:
const obj = {
    a: function() {
        console.log(this)
        window.setTimeout(() => { 
            console.log(this) 
        }, 100)
    }
  }
  obj.a()  //第一個this是obj對象,第二個this仍是obj對象
複製代碼

解析:函數obj.a沒有使用箭頭函數,由於它的this仍是obj;
而setTimeout裏的函數使用了箭頭函數,因此它會和外層的this保持一致,也是obj;
若是setTimeout裏的函數沒有使用箭頭函數,那麼它打印出來的應該是window對象。

  1. 多層對象嵌套裏函數的this
const obj = {
    a: function() { console.log(this) },
    b: {
    	c: () => {console.log(this)}
    }
  }
  obj.a()   //沒有使用箭頭函數打出的是obj
  obj.b.c()  //打出的是window對象!!
複製代碼

解析:obj.a調用後打出來的是obj對象,而obj.b.c調用後打出的是window對象而非obj, 這表示多層對象嵌套裏箭頭函數裏this是和最最外層保持一致的。



3、改變this指向的辦法

  1. call()、apply()、bind()改變this指向
//eg1:
const obj = {
    log: function() {
      console.log(this)
    }
  }
  
//1.不用變量接收:this默認指向爲obj
 obj.log() //obj
  obj.log.call(window)  //window  call和apply都會返回一個新函數
  obj.log.apply(window)  //window
  obj.log.bind(window)() //window bind則是返回改變了上下文後的一個函數,要想執行的話,還須要加個括號調用。
  
//2.用變量接收:用變量接收的話,this的默認指向就變成全局了
  var objName = obj.log
  objName()  //王
  // call、apply、bind的用法還和上面的同樣,這裏就不重複了。
複製代碼
  1. 除了call、apply、bind這三種改變this指向的辦法, 還有常規的用一個變量保存當前this指向,而後在去調用。
eg2:
  var _this = this 
  const obj = {
    log: function() {
      console.log(_this) 
    }
  }
  obj.log()//此時this的當前指向再也不是obj,而是window了。
複製代碼

小結: 3種改變this指向方法的區別

一、call和apply改變了函數的this上下文後便執行該函數,而bind則是返回改變了上下文後的一個函數。
 二、call、apply的區別: 他們倆之間的差異在於參數的區別,
    call和aplly的第一個參數都是要改變上下文的對象,而call從第二個參數開始以參數列表的形式展示,
    apply則是把除了改變上下文對象的參數放在一個數組裏面做爲它的第二個參數
複製代碼
  1. call()、apply()的一些其餘用處:求數組中的最大、最小值
let arr1 = [1, 2, 19, 6];
  //例子:求數組中的最值
  console.log(Math.max.call(null, 1,2,19,6)); // 19
  console.log(Math.max.call(null, arr1)); // NaN
  console.log(Math.max.apply(null, arr1)); //  19 直接能夠用arr1傳遞進去

複製代碼

合併數組:

var arr1=[1,2,3];
  var arr2=[4,5,6];
  arr1.push.apply(arr1,arr2);
  alert(arr1)//1,2,3,4,5,6 
  alert(arr2)//4,5,6 
複製代碼

解析:同理,apply將數組裝換爲參數列表的集合。

結尾: 以上就是對js中的this指向,和call()、apply()方法一些須要知道的小技巧。
若是以爲對你有幫助,請給做者一點小小的鼓勵,點個贊或者收藏吧。
有須要溝通的請聯繫我:微信( wx9456d ) 郵箱( allan_liu986@163.com )

相關文章
相關標籤/搜索