this指向知多少

」this「在JavaScript中很常見,用起來也很」香「,每當咱們想訪問一個值或者設置一個值,常會用到它,原生和框架都是,但它同時又讓人困惑,使咱們寫的代碼達不到預期效果,甚至引發bug,本文咱們就來看看,this的指向遵循哪些規律。編程

this等於本身?

先看幾個看似正常卻錯誤的理解。數組

指向自身

好比下面這段代碼:app

function add(num){
  //記錄add被調用的次數
  this.count++;
  console.log("計數" + num)
}
add.count = 0;

add(1)
add(2)
console.log(add.count) // 0
複製代碼

能夠看到,結果跟預期的不同,count未發生改變。框架

實際上,這段代碼無心間建立了一個全局變量 count,在執行add的時候,並未改動add的count。ide

指向所在做用域

仍是先看代碼函數

function one(){
  var a = 2;
  this.two();
}
function two(){
  console.log(this.a)
}
one();  // a is not defined
複製代碼

這種狀況略特殊,由於並不老是錯的,這裏one調用two可以成功,但卻沒法所以利用two裏面的this來訪問one裏面的a,這是作不到的。學習

看了兩個錯誤用法,怎樣是對的呢?接着往下看。ui

this是什麼

this指代變量或者方法和對象之間的從屬關係,但它是在運行時進行綁定,而不是編寫時,這取決於函數調用時的多種條件,因此,this的綁定和函數聲明的位置無關,取決於函數調用的方式this

上面這句話不難理解,由於在哪調用函數彷佛顯而易見,其實否則,在某些編程模式下,調用位置可能被隱藏,這時咱們就要順着調用的路徑找到調用位置,而後判斷它符合哪一種綁定規則。編碼

綁定規則

默認綁定

function get(){
  console.log(this.a);
}

var a = 2;
get();  // 2
複製代碼

這種屬於獨立的函數調用,應用的是函數的默認綁定,this指向全局對象。

怎麼判斷默認綁定呢,由於它是使用不帶任何修飾的函數引用進行調用的。但也要注意,只有在非嚴格模式下,this才綁定到全局,不然會提示undefined。

隱式綁定

看調用位置是否被某個對象擁有或者包含。

function get(){
  console.log(this.a);
}

var obj = {
  a:2,
  get
}
obj.get()  // 2
複製代碼

這裏咱們把get函數放在了obj內,但嚴格來講,它仍不屬於obj,只是被obj包含和調用,這個時候,this就指向了obj,this.a 就和 obj.a等價了。

只是這種狀況須要注意的是,它有個就近的規則,就是它只屬於離this最近的一層。像下面這段:

function get(){
  console.log(this.a);
}

var obj2 = {
  a:3,
  get
}

var obj1 = {
  a:2,
  obj2
}

obj1.obj2.get(); // 3
複製代碼

這段代碼中,this綁定在了obj2上,而不是一直向上追溯。

隱式丟失

一個常見的問題就是被隱式綁定的函數丟失了綁定對象,致使應用默認綁定,this就到了全局或者undefined。

第一種狀況:方法傳遞

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
  get
}

var getNum = obj.get;

var a = "global";

getNum(); // global
複製代碼

這裏咱們會發現,咱們把obj的get方法給了getNum,卻不是想象中的效果,其實咱們上面就說了,這裏的get並不真實屬於obj,而只是在調用時,this被綁定到了obj,不信你能夠像下面這麼改一下:

var a = "global";
var getNum = obj.get();
console.log(getNum)  // 3
複製代碼

又是3,跟指望的一致,這就是細微差異致使結果的不一樣。

第二種狀況:回調

function get(){
  console.log(this.a);
}

function doGet(get){
  get();
}

var obj = {
  a:3,
  get
}
var a = "global";
doGet(obj.get);  // global
複製代碼

這種狀況跟上面的相似,由於參數傳遞就是一種隱式賦值,這個時候,執行get方法的時候,仍是全局的get,this綁定的就是全局對象了。

回調很經常使用,因此,由於回調函數而丟失this的狀況也常見,甚至於,調用回調函數的函數可能會修改this,這就讓代碼行爲更加地難以捕捉。

因此有什麼好辦法彌補這些不肯定問題的發生麼。

顯式綁定

經過上面的例子能夠看到,要想把一個函數的this綁定到對象上,須要下面兩個條件:

  • 函數做爲對象的屬性
  • 經過屬性間接調用函數

若是不想這麼作呢?

1、call/apply

JavaScript中的函數都有一些有用的特性,能夠用來解決這個問題,好比:call 和 apply,由於能夠直接指定this的綁定對象,因此稱之爲顯式綁定。

可看以下代碼:

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
}
get.call(obj); // 3
複製代碼

經過get.call(),咱們把this強制綁定到了obj上。

從綁定this的角度看,call和apply的區別只是參數的形式不一樣,call能夠直接寫參數,而apply須要以數組的形式傳參。

遺憾的是,這仍沒法解決上面提到的綁定丟失問題,但它的一種變通方法能夠解決。

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
}

var getNum = function(){
  get.call(obj);
}

getNum()  // 2
複製代碼

能夠看到,這裏建立了一個函數給getNum,而後函數內部進行顯式地綁定,這樣以來,不論再怎樣調用getNum,get的this都不會變了。

2、bind

由於上面提到的綁定方式較爲經常使用,ES5直接提供了一個內置的方法——Function.prototype.bind。

function get(){
  console.log(this.a);
}

var obj = {
  a:3,
}

var getNum = get.bind(obj);

getNum()  // 3
複製代碼

bind()會返回一個硬編碼的新函數,把指定的參數設置爲this的上下文並調用原函數。

3、API調用上下文

有一些庫,或者ES新版本的內置函數,會提供一個可選參數,它的做用和bind()相似,確保回調函數使用指定的this,好比:

let arr = [1,2,3];

function get(el){
  console.log(el,this.id);
}

var obj = {
  id:"item",
}

arr.forEach(get,obj);
// 1 "item" 2 "item" 3 "item"
複製代碼

相似的這些函數實際就是經過call()或者apply()實現了顯式綁定,這樣能夠少寫一些代碼。

new綁定

咱們很熟悉的一句話:」沒有對象就new一個「。

JavaScript 中有一些內置對象函數和自定義函數的構造調用,都會用到new關鍵字,它會經歷如下過程:

  • 建立一個新對象
  • 新對象執行prototype鏈接
  • 新對象綁定到函數調用的this
  • 若是函數沒有返回其餘對象,就返回這個對象

就會有以下代碼的效果:

function Get(a){
  this.a = a;
}
var getNum = new Get(2);
console.log(getNum.a)  // 2
複製代碼

這是你們熟悉的用法,只是中間經歷的過程不是表面那麼簡單,須要理解一下。

new是本文提到的最後一種能夠影響this綁定的方法,下面看看這些規則的優先級。

規則優先級

顯然的,默認綁定優先級是最低的,它能夠輕易被改變,因此,咱們主要關心隱式綁定和顯式綁定的優先級。可按照以下順序進行判斷:

  • 是否經過new調用來綁定,是,則綁定新建立的對象
var getNum = new Get(); 
複製代碼
  • 是否直接或間接經過call、apply、bind綁定,是,綁定指定對象。
var getNum = get.call(obj);
複製代碼
  • 是否在某個上下文對象中調用,是,則綁定那個上下文對象
var  getNum = obj.get();
複製代碼
  • 若是都不是,使用默認綁定。

箭頭函數

曾有人問我,ES6當中比較喜歡哪一個設計,我沒多想,就說箭頭函數,而後他問我,它跟以前的函數有什麼不一樣,this綁定就是它們的不一樣之一。

箭頭函數不使用this的那幾種規則,而是根據外層函數/全局做用域來決定

function get(){
  return (a) =>{
    console.log(this.a);
  }
}
var obj1 = {
  a:3,
}

var obj2 = {
  a:2,
}

var getNum = get.call(obj1);
getNum.call(obj2); // 3,不是2
複製代碼

由於get內部建立的箭頭函數會捕獲調用get時的this,getNum也會跟着一塊兒綁定到obj1,且這種綁定沒法被修改。

這種就像bind方法同樣,確保函數被綁定到指定對象,它取代了傳統的this機制。

實際上,在ES6以前,咱們經常使用另一種方法來實現它。

function get(){
  var self = this;  // 就是這裏
  console.log(self.a);
}
var obj = {
  a:2
}
get.call(obj); //2
複製代碼

總結

this綁定是個看似簡單,又有一點複雜的東西,除了默認綁定和構造函數調用能夠比較天然地理解,其餘都繞了那麼一點彎兒,但也不用怕,只要通過反覆地思考和實踐,掌握它們就是本能反應了。

本文儘可能全面,仍不免疏漏,鑑於篇幅太長會增長學習負擔,索性沒有說起某些特殊狀況,多數場景已經夠用,歡迎交流探討。

下篇見!~

博客連接:this指向知多少

相關文章
相關標籤/搜索