在網上不少文章都對 Javascript 中的 this 作了詳細的介紹,但大可能是介紹各個綁定方式或調用方式下 this 的指向,因而我想有一個統一的思路來更好理解 this 指向,使你們更好判斷,如下有部份內容不是原理,而是一種解題思路。segmentfault
call 方法容許切換函數執行的上下文環境(context),即 this 綁定的對象。瀏覽器
大多數介紹 this 的文章中都會把 call 方法放到最後介紹,但此文咱們要把 call 方法放在第一位介紹,並從 call 方法切入來研究 this ,由於 call 函數是顯式綁定 this 的指向,咱們來看看它如何模擬實現(不考慮傳入 null 、 undefined 和原始值):閉包
Function.prototype.call = function(thisArg) { var context = thisArg; var arr = []; var result; context.fn = this; for (let i = 1, len = arguments.length; i < len; i++) { arr.push('arguments[' + i + ']'); } result = eval("context.fn(" + arr + ")"); delete context.fn; return result; }
從以上代碼咱們能夠看到,把調用 call 方法的函數做爲第一個參數對象的方法,此時至關於把第一個參數對象做爲函數執行的上下文環境,而 this 是指向函數執行的上下文環境的,所以 this 就指向了第一個參數對象,實現了 call 方法切換函數執行上下文環境的功能。dom
在模擬 call 方法的時候,咱們使用了對象方法來改變 this 的指向。調用對象中的方法時,會把對象做爲方法的上下文環境來調用。函數
既然 this 是指向執行函數的上下文環境的,那咱們先來研究一下調用函數時的執行上下文狀況。this
下面我門來看看調用對象方法時執行上下文是如何的:url
var foo = { x : 1, getX: function(){ console.log(this.x); } } foo.getX();
從上圖中,咱們能夠看出getX
方法的調用者的上下文是foo
,所以getX
方法中的 this 指向調用者上下文foo
,轉換成 call 方法爲foo.getX.call(foo)
。spa
下面咱們把其餘函數的調用方式都按調用對象方法的思路來轉換。prototype
function Foo(){ this.x = 1; this.getX = function(){ console.log(this.x); } } var foo = new Foo(); foo.getX();
執行 new 若是不考慮原型鏈,只考慮上下文的切換,就至關於先建立一個空的對象,而後把這個空的對象做爲構造函數的上下文,再去執行構造函數,最後返回這個對象。code
var newMethod = function(func){ var context = {}; func.call(context); return context; } function Foo(){ this.x = 1; this.getX = function(){ console.log(this.x); } } var foo = newMethod(Foo); foo.getX();
DOMElement.addEventListener('click', function(){ console.log(this); });
把函數綁定到DOM事件時,能夠看成在DOM上增長一個函數方法,當觸發這個事件時調用DOM上對應的事件方法。
DOMElement.clickHandle = function(){ console.log(this); } DOMElement.clickHandle();
var x = 1; function getX(){ console.log(this.x); } getX();
這種狀況下,咱們建立一個虛擬上下文對象,而後普通函數做爲這個虛擬上下文對象的方法調用,此時普通函數中的this就指向了這個虛擬上下文。
那這個虛擬上下文是什麼呢?在非嚴格模式下是全局上下文,瀏覽器裏是 window ,NodeJs裏是 Global ;在嚴格模式下是 undefined 。
var x = 1; function getX(){ console.log(this.x); } [viturl context].getX = getX; [viturl context].getX();
var x = 1; var foo = { x: 2, y: 3, getXY: function(){ (function(){ console.log("x:" + this.x); console.log("y:" + this.y); })(); } } foo.getXY();
這段代碼的上下文以下圖:
這裏須要注意的是,咱們再研究函數中的 this 指向時,只須要關注 this 所在的函數是如何調用的, this 所在函數外的函數調用都是浮雲,是不須要關注的。所以在全部的圖示中,咱們只須要關注紅色框中的內容。
所以這段代碼咱們關注的部分只有:
(function(){ console.log(this.x); })();
與普通函數調用同樣,建立一個虛擬上下文對象,而後普通函數做爲這個虛擬上下文對象的方法當即調用,匿名函數中的 this 也就指向了這個虛擬上下文。
var x = 1; var foo = { x: 2, getX: function(){ console.log(this.x); } } setTimeout(foo.getX, 1000);
函數參數是值傳遞的,所以上面代碼等同於如下代碼:
var getX = function(){ console.log(this.x); }; setTimeout(getX, 1000);
而後咱們又回到了普通函數調用的問題。
全局中的 this 指向全局的上下文
var x = 1; console.log(this.x);
var x = 1; var a = { x: 2, b: function(){ return function(){ return function foo(){ console.log(this.x); } } } }; (function(){ var x = 3; a.b()()(); })();
看到上面的狀況是有不少個函數,但咱們只須要關注 this 所在函數的調用方式,首先咱們來簡化一下以下:
var x = 1; (function(){ var x = 3; var foo = function(){ console.log(this.x); } foo(); });
this 所在的函數 foo 是個普通函數,咱們建立一個虛擬上下文對象,而後普通函數做爲這個虛擬上下文對象的方法當即調用。所以這個 this指向了這個虛擬上下文。在非嚴格模式下是全局上下文,瀏覽器裏是 window ,NodeJs裏是 Global ;在嚴格模式下是 undefined 。
在須要判斷 this 的指向時,咱們能夠安裝這種思路來理解:
判斷 this 在全局中OR函數中,若在全局中則 this 指向全局,若在函數中則只關注這個函數並繼續判斷。
判斷 this 所在函數是否做爲對象方法調用,如果則 this 指向這個對象,不然繼續操做。
建立一個虛擬上下文,並把this所在函數做爲這個虛擬上下文的方法,此時 this 指向這個虛擬上下文。
在非嚴格模式下虛擬上下文是全局上下文,瀏覽器裏是 window ,Node.js裏是 Global ;在嚴格模式下是 undefined 。
圖示以下:
歡迎關注:Leechikit
原文連接:segmentfault.com到此本文結束,歡迎提問和指正。寫原創文章不易,若本文對你有幫助,請點贊、推薦和關注做者支持。