在理解this綁定之前,先要理解調用位置。
我們可以通過瀏覽器的調試工具來查看調用棧。
在第一行代碼錢插入一條debugger;語句,運行代碼,可以看到當前位置函數的調用列表(call stack),找到棧中的第二個元素,就是真正的調用位置。

聲明:下面很多內容使用的是《You Don’t Kown JavaScript》中的思想。
this的綁定對象———先找到函數的執行過程中調用位置,然后判斷應用了下面四條綁定規則的哪一條。
**this的四種綁定規則:**
1、默認綁定
2、隱式綁定
3、顯示綁定
4、new綁定
### 默認綁定
首先從默認綁定開始,在無法應用其他規則時的默認規則,通常是獨立函數調用。

在調用add()時,應用了默認綁定,this指向全局對象(非嚴格模式下),嚴格模式下,this指向undefined。

### 隱式綁定
調用位置是否有上下文對象/被某個對象“擁有”或“包含”(但不是真正的擁有)

add函數聲明在外部,嚴格來說并不屬于instance,但是在調用add時,調用位置會使用instance的上下文來引用函數,隱式綁定會把函數調用中的this(即此例add函數中的this)綁定到這個上下文對象(即此例中的instance)
需要注意的是:對象屬性鏈中只有最后一層會影響到調用位置。

隱式綁定有一個問題,就是隱式丟失(在第一篇中提到過的綁定丟失的問題)

綁定丟失的另一種情況發生在回調函數中。

我的理解是:fun(instance.add),做了一個隱式的賦值操作,var fn = instance.add,然后運行fn();這樣就跟上面的例子一樣了。
本來看書上說做了一個隱式賦值,自己并未理解,但是想努力地說清楚這個問題,于是自己思考,反而與書中不謀而合,本來沒明白他說得隱式賦值操作是什么。
到這個地方,就不得不提起setTimeout了。

setTimeout(person1.grow, 100); //undefined
實話說,我原本是一個似理解又不是很理解的一個狀態,現在算是豁然開朗了。
setTimeout(fn,delay){
fn();
}
與上面的例子其實是一樣的。
除了上面的兩種情況以外,還有一種情況this的行為會出乎我們意料:調用回調函數的函數可能會修改this,在一些流行的JS庫中事件處理器常會把毀掉函數的this強制綁定到觸發事件的DOM元素上。(關于這個問題,暫未研究,因此也不予舉例說明)
### 顯式綁定
顯式綁定就是通過call()或者apply()函數指定this的綁定對象。

不過顯式綁定還是未能解決綁定丟失的問題,如下面的代碼,結果依舊為50。

因此我們引入“硬綁定”這個概念。注意下面例子中劃紅色橫線的部分,根據書中說硬綁定之后,無法再修改它的this值,應該是我紅色寫得部分,即(fn.call(instance))這種寫法,無論fun.call()第一個參數傳什么,都不能再改變它的this值,相反,我例子中的寫法,是需要和外圍配合的,即需要傳什么this值,就去指定它。

**硬綁定**是能夠解決綁定丟失的問題的,如下:

ES5中提供了內置的方法進行硬綁定,在第一篇this詞法的博文中也提及過,即bind. Function.prototype.bind,用法如下:

上面的兩句代碼可以用那一句紅色的代碼代替,需要注意的是:
bind是function對象的方法,千萬不能寫成add().bind(instance);因為這個add()函數中返回值是undefined,而undefined顯然是沒有bind方法的。
如果是下面這樣,那顯然又是不一樣的。

這兒使用的是add().bind,原因也很簡單,因為add()返回的是一個函數。
**API調用的“上下文”**

順便了解一下forEach的用法。forEach是一個JavaScript擴展到ECMA-262標準;因此它可能不存在在標準的其他實現。為了使它工作,可以增加下面的代碼:
~~~
if(!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun)
{
varlen = this.length;
if(typeoffun != "function"){
thrownewTypeError();
}
varself= arguments[1];
for(vari = 0; i < len; i++)
{
if(i inthis)
fun.call(self, this[i], i, this);
}
};
}
~~~
對每個函數成員執行callback函數,并且可以指定this的值。
### new綁定
javaScript和C++不一樣,并沒有類,在javaScript中,構造函數只是使用new操作符時被調用的函數,這些函數和普通的函數并沒有什么不同,它不屬于某個類,也不可能實例化出一個類。任何一個函數都可以使用new來調用,因此其實并不存在構造函數,而只有對于函數的“構造調用”。
使用new來調用函數,會自動執行下面的操作:
1、創建一個新對象
2、這個新對象會被執行[[原型]]連接
3、這個新對象會綁定到函數調用的this
4、如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象。

instance綁定到Add的this。
優先級
了解了函數調用中this綁定的四條規則之后,需要做的就是找到函數的調用位置并判斷應當應用哪條規則。當某個調用位置可以應用多條規則時,就需要了解這些綁定規則的優先級。
關于優先級的問題,不再細說,只給出結果,只說下顯式綁定和new綁定優先級。
默認綁定 < 隱式綁定 < 顯式綁定 < new綁定
new和call/apply無法一起使用,因此無法通過測試new add.call(obj)的方式來直接進行測試,但是可以使用硬綁定來測試他們的優先級

當硬綁定函數被new調用時,會使用新創建的this替換硬綁定的this。當然再次使用硬綁定,我們還是可以將this綁定在其它的對象上的。

總結一下,判斷this的步驟:
1.函數是否在new中調用,即是否是new綁定,如果是new綁定,那么this綁定的是新創建的對象。
2.函數是否是顯示綁定(call,apply,bind),如果是,那么this綁定的是指定的對象
3.函數是否在某個上下文對象中調用,即隱式綁定,較多的是在某個對象中調用,如果是的話,this綁定的是那個上下文對象。
4.如果以上規則都不能應用,那么就是默認綁定,在嚴格模式下,this被綁定到undefined,在非嚴格模式下,綁定到全局對象。
最后,我們來看下
### 綁定例外
:
**1、被忽略的this**
如果我們將null或者是undefined作為this的綁定對象傳入call、apply或者是bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。

實際應用的是默認綁定規則,add中的this被綁定到了全局對象(非嚴格模式)
在使用apply()來“展開”一個數組,并當做一個參數傳入一個函數,或使用bind()對參數進行柯里化(即預先設置好一些參數),bind和apply都需要傳入一個參數作為this的綁定對象,即使函數不關心this的值,仍然需要傳入一個占位值,這是null或者是undefined可能就是一個不錯的選擇。

上面說可能是一個不錯的選擇,那么也就是說這其實并不是一個比較好的做法,因為如果某個函數確實使用了this,那默認綁定規則會把this綁定到全局對象,這就可能會產生難以預料的問題。
一個更安全的做法是傳入一個特殊的對象,把this綁定到這個對象則不會對程序產生副作用,即使某個函數中使用了this,也不會影響到全局對象。
首先創建一個空對象:Φ(數學中的空集符號,既能表達意思,而且不易重名)

**2、間接引用**
可能會無意間創建一個函數的“間接引用”,在這種情況下,調用這個函數會引用默認綁定規則。間接引用最容易在賦值時發生。

這種情況下調用位置是add(),而不是example.add()或者是instance.add(),應用默認綁定。
想加個debugger看看,然后引發出了一個新問題,如下:期望得到解答。

**3、軟綁定**
硬綁定可以將this的值強制綁定到指定的對象,防止函數調用應用默認綁定規則,但是,硬綁定同樣會帶來一個靈活性的問題,用硬綁定之后就無法使用隱式綁定或者顯式綁定去修改this的值,只能通過new或者重寫硬綁定。
如果給默認綁定指定一個全局對象和undefined/null以外的值,那就可以實現和硬綁定相同的效果,同時可以保留隱式綁定或者顯式綁定修改this的能力。
softBind()的原理和bind()類似,會對指定的函數進行封裝,首先檢查調用時的this,如果this綁定到全局對象或者是undefined,那么就把指定的默認對象的obj綁定到this,否則不會修改this.
~~~
if(!Function.prototype.softBind){
Function.prototype.softBind = function(obj){
var fn = this;
var curried = [].slice.call( arguments,1);
var bound = function(){
return fn.apply(
(!this || this === (window || global)) ? obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
~~~

最后簡單說下ES6中的箭頭函數,其實在第一篇關于this詞法的博文中已經提及過了,再鞏固一下吧。
前面所說的四種規則,只適用于普通的函數,對于ES6中的箭頭函數是不起作用的。箭頭函數不適用以上的規則,它根據外層的作用域來決定this.


對比這兩個,應該能明顯得看出問題吧,箭頭函數的綁定是無法修改的,它被綁定到instance之后,即使我們通過call再去指定this的值,它也不會搭理我們,即使適用等級最高的new也是不行的,這里,適用new操作符,最終它只會華麗麗得返回給你一個undefined。
箭頭函數事實上和增加一句self = this效果基本一致。無論是self = this或者是箭頭函數,都想用替代this機制。
建議:
在編寫代碼時,應當:
1、只采用詞法作用域并完全拋棄this風格的代碼;
2、完全采用this風格,在必要的時候使用bind(),盡量避免使用self=this和箭頭函數(出自Kyle Simpson)
到此,this詞法就暫且告一段落,后續只會在有什么頓悟的時候再補充了。
本來已經看得有點崩潰,但是為了對讀到此文的讀者負責,還是看完了所有的關于這部分的內容,此前已經看了兩遍,希望本文對您理解JS的this詞法能有一點幫助。
- 前言
- jQuery輪播圖插件
- JS模擬事件操作
- JS閉包與變量
- JS綁定事件
- HTML5之file控件
- JavaScript的this詞法
- JavaScript的this詞法(二)
- JS this詞法(三)
- JS檢測瀏覽器插件
- JS拖拽組件開發
- number輸入框
- Modernizr.js和yepnode.js
- DOM變化后事件綁定失效
- div和img之間的縫隙問題
- 詳解JavaScript作用域
- bootstrap入門
- 表單驗證(登錄/注冊)
- Bootstrap網格系統
- Bootstrap排版
- Bootstrap創建表單(一)
- Bootstrap表單(二)
- Bootstrap按鈕
- Bootstrap圖片
- Bootstrap字體圖標(glyphicons)
- Bootstrap的aria-label和aria-labelledby
- Bootstrap下拉菜單
- Bootstrap按鈕組
- Bootstrap按鈕菜單
- Bootstrap輸入框組
- Bootstrap導航元素
- Bootstrap導航欄
- sublimeText頻頻崩潰
- JQuery不同版本的差異(checkbox)
- Bootstrap面包屑導航、分頁、標簽、徽章
- Bootstrap警告
- Bootstrap進度條
- 前端的上傳下載
- JS字符串的相關方法
- CSS3選擇器(全)
- CSS3新增文本屬性詳述
- 利用CSS3實現圖片切換特效
- CSS3新增顏色屬性
- CSS3的border-radius屬性詳解
- JS創建對象幾種不同方法詳解
- JS實現繼承的幾種方式詳述(推薦)
- CSS3響應式布局
- JS模塊化開發(requireJS)
- 利用@font-face實現個性化字體
- 前端在html頁面之間傳遞參數的方法
- CSS自動換行、強制不換行、強制斷行、超出顯示省略號
- 如何在Html中引入外部頁面
- reactJS入門
- React組件生命周期
- 使用React實現類似快遞單號查詢效果
- ReactJS組件生命周期詳述
- React 屬性和狀態詳解