詳解JavaScript中的this

foocoder發表於2013-05-08

  JavaScript中的this總是讓人迷惑,應該是js眾所周知的坑之一。 個人也覺得js中的this不是一個好的設計,由於this晚繫結的特性,它可以是全域性物件,當前物件,或者…有人甚至因為坑大而不用this。

  其實如果完全掌握了this的工作原理,自然就不會走進這些坑。來看下以下這些情況中的this分別會指向什麼:

  1.全域性程式碼中的this

alert(this)//window

  全域性範圍內的this將會指向全域性物件,在瀏覽器中即使window。

  2.作為單純的函式呼叫

function fooCoder(x) {
	this.x = x;
}
fooCoder(2);
alert(x);// 全域性變數x值為2

  這裡this指向了全域性物件,即window。在嚴格模式中,則是undefined。

  3.作為物件的方法呼叫

var name = "clever coder";
var person = {
	name : "foocoder",
	hello : function(sth){
		console.log(this.name + " says " + sth);
	}
}
person.hello("hello world");

  輸出 foocoder says hello world。this指向person物件,即當前物件。

  4.作為建構函式

new FooCoder();

  函式內部的this指向新建立的物件。

  5.內部函式

var name = "clever coder";
var person = {
	name : "foocoder",
	hello : function(sth){
		var sayhello = function(sth) {
			console.log(this.name + " says " + sth);
		};
		sayhello(sth);
	}
}
person.hello("hello world");//clever coder says hello world

  在內部函式中,this沒有按預想的繫結到外層函式物件上,而是繫結到了全域性物件。這裡普遍被認為是JavaScript語言的設計錯誤,因為沒有人想讓內部函式中的this指向全域性物件。一般的處理方式是將this作為變數儲存下來,一般約定為that或者self:

var name = "clever coder";
var person = {
	name : "foocoder",
	hello : function(sth){
		var that = this;
		var sayhello = function(sth) {
			console.log(that.name + " says " + sth);
		};
		sayhello(sth);
	}
}
person.hello("hello world");//foocoder says hello world

  6.使用call和apply設定this

person.hello.call(person, "world");

  apply和call類似,只是後面的引數是通過一個陣列傳入,而不是分開傳入。兩者的方法定義:

call( thisArg [,arg1,arg2,… ] );  // 引數列表,arg1,arg2,...
apply(thisArg [,argArray] );     // 引數陣列,argArray

  兩者都是將某個函式繫結到某個具體物件上使用,自然此時的this會被顯式的設定為第一個引數。

  簡單地總結

  簡單地總結以上幾點,可以發現,其實只有第六點是讓人疑惑的。

  其實就可以總結為以下幾點:

  1.當函式作為物件的方法呼叫時,this指向該物件。

  2.當函式作為淡出函式呼叫時,this指向全域性物件(嚴格模式時,為undefined)

  3.建構函式中的this指向新建立的物件

  4.巢狀函式中的this不會繼承上層函式的this,如果需要,可以用一個變數儲存上層函式的this。

  再總結的簡單點,如果在函式中使用了this,只有在該函式直接被某物件呼叫時,該this才指向該物件。

obj.foocoder();
foocoder.call(obj, ...);
foocoder.apply(obj, …);

  更進一步

  我們可能經常會寫這樣的程式碼:

$("#some-ele").click = obj.handler;

  如果在handler中用了this,this會繫結在obj上麼?顯然不是,賦值以後,函式是在回撥中執行的,this會繫結到$(“#some-div”)元素上。這就需要理解函式的執行環境。本文不打算長篇贅述函式的執行環境,可以參考《JavaScript高階程式設計》中對執行環境和作用域鏈的相關介紹。這裡要指出的時,理解js函式的執行環境,會更好地理解this。

  那我們如何能解決回撥函式繫結的問題?ES5中引入了一個新的方法,bind():

fun.bind(thisArg[, arg1[, arg2[, ...]]])

thisArg
當繫結函式被呼叫時,該引數會作為原函式執行時的this指向.當使用new 操作符呼叫繫結函式時,該引數無效.
arg1, arg2, ...
當繫結函式被呼叫時,這些引數加上繫結函式本身的引數會按照順序作為原函式執行時的引數.

  該方法建立一個新函式,稱為繫結函式,繫結函式會以建立它時傳入bind方法的第一個引數作為this,傳入bind方法的第二個以及以後的引數加上繫結函式執行時本身的引數按照順序作為原函式的引數來呼叫原函式.

  顯然bind方法可以很好地解決上述問題。

$("#some-ele").click(person.hello.bind(person));
//相應元素被點選時,輸出foocoder says hello world

  其實該方法也很容易模擬,我們看下Prototype.js中bind方法的原始碼:

Function.prototype.bind = function(){
  var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift();
  return function(){
    return fn.apply(object,
      args.concat(Array.prototype.slice.call(arguments)));
  };
};

  明白了麼?

  相信看完全文以後,this不再是坑~

相關文章