JavaScript中的this

前言

總括:詳解JavaScript中的this的一篇總結,不懂this這個難點,不少時候會形成一些困擾,寫出一些bug不知如何收場,因此一塊兒來寫bug吧,不對,一塊兒來寫代碼吧。javascript

人生得意須盡歡,莫使金樽空對月瀏覽器

正文

JavaScript中的this格外的不同,好比Java語言中的this是在代碼的執行階段是不可更改,而JavaScript的this是在調用階段進行綁定。👌由於這一性質因此給了this很大的發揮空間。但其在嚴格模式和非嚴格模式下又有些不同,在函數的不一樣調用方式也致使this有些區別。🌹app

What's this?

😎首先對this的下個定義:this是在執行上下文建立時肯定的一個在執行過程當中不可更改的變量。函數

所謂執行上下文,就是JavaScript引擎在執行一段代碼以前將代碼內部會用到的一些變量函數this提早聲明而後保存在變量對象中的過程。這個'代碼片斷'包括:全局代碼(script標籤內部的代碼)、函數內部代碼eval內部代碼。而咱們所熟知的做用域鏈也會在保存在這裏,以一個類數組的形式存儲在對應函數的[[Scopes]]屬性中。ui

this只在函數調用階段肯定,也就是執行上下文建立的階段進行賦值,保存在變量對象中。這個特性也致使了this的多變性:🙂即當函數在不一樣的調用方式下均可能會致使this的值不一樣。this

👆👆上面咱們說過了在嚴格模式下和非嚴格模式下this表現不一樣:spa

var a = 1;
function fun() {
 'use strict';
    var a = 2;
      return this.a;
}
fun();//😨報錯 Cannot read property 'a' of undefined複製代碼

👆嚴格模式下,this指向undefined;

var a = 1;
function fun() {
    var a = 2;
      return this.a;
}
fun();//1複製代碼

👆非嚴格模式下this指向window;

上面同一段代碼,在不一樣模式下之因此有不一樣表現,就是由於this在嚴格模式,非嚴格模式下的不一樣。

結論:當函數獨立調用的時候,在嚴格模式下它的this指向undefined,在非嚴格模式下,當this指向undefined的時候,自動指向全局對象(瀏覽器中就是window)

多提一句,在全局環境下,this就是指向本身,再看🌰:

this.a = 1;
var b = 1;
c = 1;
console.log(this === window)//true
//這三種都能獲得想要的結果,全局上下文的變量對象中存在這三個變量複製代碼

再多提一句,當this不在函數中用的時候會怎樣?看🌰:

var a = 1000;
var obj = {
    a: 1,
      b: this.a + 1
}
function fun() {
    var obj = {
          a: 1,
        c: this.a + 2 //嚴格模式下這塊報錯 Cannot read property 'a' of undefined
    }
    return obj.c;
}
console.log(fun());//1002
console.log(obj.b);//1001複製代碼

這種狀況下this仍是指向了window。那麼咱們能夠單獨下個結論:

當obj在全局聲明的時候,obj內部屬性中的this指向全局對象,當obj在一個函數中聲明的時候,嚴格模式下this會指向undefined,非嚴格模式自動轉爲指向全局對象。

👌好了,剛剛小試牛刀下,知道了嚴格模式和非嚴格模式下this的區別,然而咱們平常應用最多的仍是在函數中用this,上面也說過了this在函數的不一樣調用方式還有區別,那麼函數的調用方式都有哪些呢?四種:

  • 在全局環境或是普通函數中直接調用
  • 做爲對象的方法
  • 使用apply和call
  • 做爲構造函數

下面分別就四種狀況展開:

直接調用

上面的🌰其實就是直接調用的,不過我決定再寫☝️🌰:

var a = 1;
var obj  =  {
    a: 2,
      b: function () {
        function fun() {
          return this.a
        }
       console.log(fun());
    }
} 
obj.b();//1複製代碼

fun函數雖然在obj.b方法中定義,但它仍是一個普通函數,直接調用在非嚴格模式下指向undefined,又自動指向了全局對象,正如預料,嚴格模式會報錯undefined.a不成立,a未定義。

重要的事情再說一遍:當函數獨立調用的時候,在嚴格模式下它的this指向undefined,在非嚴格模式下,當this指向undefined的時候,自動指向全局對象(瀏覽器中就是window)。😯

做爲對象的方法

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

👆b所引用的匿名函數做爲obj的一個方法調用,這時候this指向調用它的對象。這裏也就是obj。那麼若是b方法不做爲對象方法調用呢?啥意思呢,就是這樣👇:

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
}
var t = obj.b;
console.log(t());//1複製代碼

如上,t函數執行結果居然是全局變量1,爲啥呢?這就涉及Javascript的內存空間了,就是說,obj對象的b屬性存儲的是對該匿名函數的一個引用,能夠理解爲一個指針。當賦值給t的時候,並無單獨開闢內存空間存儲新的函數,而是讓t存儲了一個指針,該指針指向這個函數。至關於執行了這麼一段僞代碼:

var a = 1;
function fun() {//此函數存儲在堆中
    return this.a;
}
var obj = {
  a: 2,
  b: fun //b指向fun函數
}
var t = fun;//變量t指向fun函數
console.log(t());//1複製代碼

此時的t就是一個指向fun函數的指針,調用t,至關於直接調用fun,套用以上規則,打印出來1天然很好理解了。

使用apply,call

關於apply和call是幹什麼的怎麼用本文不涉及,請移駕:applycall

這是個萬能公式,實際上上面直接調用的代碼,咱們能夠當作這樣的:

function fun() {
      return this.a;
}
fun();//1
//嚴格模式
fun.call(undefined)
//非嚴格模式
fun.call(window)複製代碼

這時候咱們就能夠解釋下,爲啥說在非嚴格模式下,當函數this指向undefined的時候,會自動指向全局對象,如上,在非嚴格模式下,當調用fun.call(undefined)的時候打印出來的依舊是1,就是最好的證據。

爲啥說是萬能公式呢?再看函數做爲對象的方法調用:

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
}
obj.b()
obj.b.call(obj)複製代碼

如上,是否是很強大,能夠理解爲其它兩種都是這個方法的語法糖罷了,那麼apply和call是否是真的萬能的呢?並非,ES6的箭頭函數就是特例,由於箭頭函數的this不是在調用時候肯定的,這也就是爲啥說箭頭函數好用的緣由之一,由於它的this固定不會變來變去的了。關於箭頭函數的this咱們稍後再說。

做爲構造函數

何爲構造函數?所謂構造函數就是用來new對象的函數,像FunctionObjectArrayDate等都是全局定義的構造函數。其實每個函數均可以new對象,那些批量生產咱們須要的對象的函數就叫它構造函數罷了。注意,構造函數首字母記得大寫。

function Fun() {
  this.name = 'Damonre';
  this.age = 21;
  this.sex = 'man';
  this.run = function () {
    return this.name + '正在跑步';
  }
}
Fun.prototype = {
  contructor: Fun,
  say: function () {
    return this.name + '正在說話';
  }
}
var f = new Fun();
f.run();//Damonare正在跑步
f.say();//Damonare正在說話複製代碼

如上,若是函數做爲構造函數用,那麼其中的this就表明它即將new出來的對象。爲啥呢?new作了啥呢?

僞代碼以下:

function Fun() {
  //new作的事情
  var obj = {};
  obj.__proto__ = Fun.prototype;//Base爲構造函數
  obj.name = 'Damonare';
  ...//一系列賦值以及更多的事
  return obj
}複製代碼

也就是說new作了下面這些事:

  • 建立一個臨時對象
  • 給臨時對象綁定原型
  • 給臨時對象對應屬性賦值
  • 將臨時對象return

也就是說new其實就是個語法糖,this之因此指向臨時對象仍是沒逃脫上面說的幾種狀況。

固然若是直接調用Fun(),以下:

function Fun() {
  this.name = 'Damonre';
  this.age = 21;
  this.sex = 'man';
  this.run = function () {
    return this.name + '正在跑步';
  }
}
Fun();
console.log(window)複製代碼

其實就是直接調用一個函數,this在非嚴格模式下指向window,你能夠在window對象找到全部的變量。

另外還有一點,prototype對象的方法的this指向實例對象,由於實例對象的__proto__已經指向了原型函數的prototype。這就涉及原型鏈的知識了,即方法會沿着對象的原型鏈進行查找。

箭頭函數

剛剛提到了箭頭函數是一個不能夠用call和apply改變this的典型。

咱們看下面這個🌰:

var a = 1;
var obj = {
  a: 2
};
var fun = () => console.log(this.a);
fun();//1
fun.call(obj)//1複製代碼

以上,兩次調用都是1。

那麼箭頭函數的this是怎麼肯定的呢?箭頭函數會捕獲其所在上下文的 this 值,做爲本身的 this,也就是說箭頭函數的this在詞法層面就完成了綁定。apply,call方法只是傳入參數,卻改不了this。

var a = 1;
var obj = {
  a: 2
};
function fun() {
    var a = 3;
    let f = () => console.log(this.a);
      f();
};
fun();//1
fun.call(obj);//2複製代碼

如上,fun直接調用,fun的上下文中的this值爲window,注意,這個地方有點繞。fun的上下文就是此箭頭函數所在的上下文,所以此時f的this爲fun的this也就是window。當fun.call(obj)再次調用的時候,新的上下文建立,fun此時的this爲obj,也就是箭頭函數的this值。

再來一個🌰:

function Fun() {
    this.name = 'Damonare';
}
Fun.prototype.say = () => {
    console.log(this);
}
var f = new Fun();
f.say();//window複製代碼

有的同窗看到這個🌰會很懵逼,感受上this應該指向f這個實例對象啊。不是的,此時的箭頭函數所在的上下文是__proto__所在的上下文也就是Object函數的上下文,而Object的this值就是全局對象。

那麼再來一個🌰:

function Fun() {
    this.name = 'Damonare';
      this.say = () => {
        console.log(this);
    }
}
var f = new Fun();
f.say();//Fun的實例對象複製代碼

如上,this.say所在的上下文,此時箭頭函數所在的上下文就變成了Fun的上下文環境,而由於上面說過當函數做爲構造函數調用的時候(也就是new的做用)上下文環境的this指向實例對象。

後記

文中定義均爲我的總結,不妥之處還請雅正。

轉載請註明出處。

以上。

相關文章
相關標籤/搜索