讀you don't konw js 有感之this全面解析

本文默認讀者已經對this有了一定了解,如果讀者覺得自己對this定義或者this的簡單指向還有疑問,請先看我的另一篇文章

this直譯過來就是這個,它會被自動定義到所有函數的作用域中,不過這也造成了很多困擾,因爲經常很難分辨它到底代表着什麼,但由於它自身的功能確實可以提供很多便利,所以程序員不管能不能掌握this,不管會經常報錯,還是會樂此不彼的使用它。

那麼this到底是什麼?其實它是函數在運行時根據一定規則綁定到變量上的,在函數被調動時,會創建一個調用的記錄,也被稱爲執行上下文,這個記錄中記錄着調用棧(函數的調用次序),函數參數,調用方式等,this是這個記錄的一個屬性。

簡單介紹一下四種綁定機制:默認綁定(函數調用),隱式綁定(方法調用),顯式綁定(call,apply調用),new綁定(構造器調用)。這四種機制已經在另一篇文章介紹過了,這裏就不再贅述了,我們今天要討論的是這四種機制的優先級和一些特殊情況。

優先級:

毫無疑問,默認綁定是最低級別的,所以我們就不考慮它了。判斷一下顯式綁定和隱式綁定

var foo = function foo(){
    console.log(this.a);
}

var obj1 = {
    a: 1,
    foo: foo
};

var obj2 = {
    a:2,
    foo: foo
};

obj1.foo();//1
obj2.foo();//2

obj1.foo().call(obj2);//2
obj2.foo().call(obj1);//1
所以顯示綁定比隱式綁定優先級更高。


接下來判斷一下new綁定和顯示綁定

var foo = function foo( param ){
  this.a = param;
};

var obj1 = {};

var F = foo.bind(obj1);
F(1);
console.log(obj1.a);//1

var f = new F(2);
console.log(obj1.a);//1
console.log(f.a);//2
可以看出在new了一個實例後,其返回的新對象已經被改變了this綁定,但是其實原來的對象並沒有被改變。所以最後的結果就是new綁定 顯式綁定 隱式綁定 默認綁定。

但是我們的js總是會給我們帶來無限驚喜,這些規則也並不是鐵律。在使用apply、call時,我們會遇到一種場景:不需要指定this的指向,只是想利用一下它的後面傳入的參數,這時我們經常會將null傳入作爲第一個參數,然而這會導致一個非常不好的後果,這時this其實會指向全局對象,也就是其實會使用默認規則,其實這也好理解,你把它指向設置爲null,它不變成null就只能恢復默認設置了。這裏我們提供一種解決方案,傳入一個空的對象,這樣就不會對全局對象產生影響了。

  另外介紹一下軟綁定,顧名思義與硬綁定相對,軟綁定是指給函數綁定一個非全局變量和undefined的值,保證不會讓this恢復默認綁定的同時還給以後靈活改變this綁定留有餘地。下面給出軟綁定的實現。

        if( !Function.prototype.softBind ){//是否已經存在softBind函數
		Function.prototype.softBind = function(obj){
			//傳入要軟綁定this的對象以及參數
			var fn = this;//保留原函數的引用,也就是Function
			var curried = [].slice.call( arguments, 1 );//提取出傳入參數的非綁定對象部分

			var bound = function(){
				return fn.apply(
					( !this || this === (window || global )) ? obj : this,//如果this對象不爲空或者不是全局對象就綁定傳入的對象  this由bound的調用方式決定
					curried.concat.apply( curried, arguments )//將初始參數和新參數合併
				);
			};

			bound.prototype = Object.create( fn.prototype );//創建一個繼承原函數原型的空對象,並將bound的原型設爲它
			return bound;

		}
	}

	function foo(){
		console.log( "name: " + this.name );
	}

	var obj = { name: "obj" },
		obj2 = { name: "obj2" },
		obj3 = { name: "obj3" };

	var fooOBJ = foo.softBind( obj );
	fooOBJ();// obj

	obj2.foo = foo.softBind( obj );
	obj2.foo();//obj2

	fooOBJ.call( obj3 );//obj3

	setTimeout( obj2.foo, 10 );//obj
其實代碼思路很簡單,就是在call、apply系列函數的基礎上加了一個額外的判定功能,判定this是不是默認綁定,如果是默認綁定則啓用我們軟綁定的對象,其根本目的就是防止this默認綁定,也可以理解爲我們手動給this設置了默認綁定。

另外還有一個需要注意的地方是es6中定義的箭頭函數,他的this是直接根據外層作用域來決定的。這裏就多敘述了,我將在接下來es6專題中討論它。



發佈了49 篇原創文章 · 獲贊 31 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章