前言: 名字取得可能有點大,this 關鍵字是 JavaScript 中最複雜的機制之一。筆者也困擾已久,但自從閱讀了《你不知道的Javascript》之後豁然開朗,整理成文。如需更更詳細的解釋,請閱讀《你不知道的Javascript》第二部分第1章第2章。es6
最經常使用的函數調用類型:獨立函數調用。能夠把這條規則看做是沒法應用 其餘規則時的默認規則。app
function foo() { console.log( this.a ); } var a = 2; foo(); // 2
foo() 是直接使用不帶任何修飾的函數引用進行調用的,所以只能使用 默認綁定,沒法應用其餘規則。
ps:不帶任何修飾的函數的描述若是有疑惑的話請不要糾結,繼續看下去就就會明白。框架
須要說明的一點是:若是使用嚴格模式(strict mode),那麼全局對象將沒法使用默認綁定,所以 this 會綁定 到 undefined:函數
function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // TypeError: this is undefined
另外一條須要考慮的規則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包 含,不過這種說法可能會形成一些誤導。oop
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 此處foo裏的this就是指的.前面的對象
ps:囉嗦兩句,爲何obj.foo()能夠執行,這是一種屬性的引用鏈的寫法,由於obj 和foo 都掛在全局做用域上。若是還不明白再舉一個例子。this
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置.此處也就是obj2編碼
這裏說一個比較容易出錯的地方:es5
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局對象的屬性 setTimeout( obj.foo, 100 ); // "oops, global
JavaScript 環境中內置的 setTimeout() 函數實現和下面的僞代碼相似:prototype
function setTimeout(fn,delay) { // 等待 delay 毫秒code
fn(); // <-- 調用位置! }
setTimeout的參數fn能夠看作調用時候傳入的函數複製給fn,這個是掛在全局做用域上的(此處的說法不嚴謹,不少時候框架的不一樣此處的this被綁定到哪兒很不肯定).因此,回調函數丟失 this 綁定是很是常見的。es6 的=>很好的解決了這個問題。
JavaScript 提供的絕大多數函數以及你自 己建立的全部函數均可以使用 call(..) 和 apply(..) 方法。它們會把這個對象綁定到 this,接着在調用函數時指定這個 this。由於你能夠直接指定 this 的綁定對象,所以我 們稱之爲顯式綁定。
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2
經過 foo.call(..),咱們能夠在調用 foo 時強制把它的 this 綁定到 obj 上。
若是你傳入了一個原始值(字符串類型、布爾類型或者數字類型)來看成 this 的綁定對 象,這個原始值會被轉換成它的對象形式(也就是 new String(..)、new Boolean(..) 或者 new Number(..))。這一般被稱爲「裝箱」。
ps:硬綁定只能執行一次,以後再綁別的都是綁不上去的。
因爲硬綁定是一種很是經常使用的模式,因此在 ES5 中提供了內置的方法 Function.prototype. bind,它的用法以下:
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = foo.bind( obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5
bind(..) 會返回一個硬編碼的新函數,它會把參數設置爲 this 的上下文並調用原始函數。
對於js的new 初學者可能會把它和JAVA類的語言混淆
首先咱們從新定義一下 JavaScript 中的「構造函數」 。在 JavaScript 中,構造函數只是一些 使用 new 操做符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上, 它們甚至都不能說是一種特殊的函數類型,它們只是被 new 操做符調用的普通函數而已。
2涉及到原型鏈的知識,本篇可忽視。
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
使用 new 來調用 foo(..) 時,咱們會構造一個新對象並把它綁定到 foo(..) 調用中的 this 上。new 是最後一種能夠影響函數調用時 this 綁定行爲的方法,咱們稱之爲 new 綁定。
ps:此處可能看得比較費解,請閱讀《你不知道的js》第二部的關於類與原型鏈的章節,仍是比較複雜的,在此就不展開了。此處你能夠理解爲用函數new 了一個新對象,而後綁定到 函數的this上使用。
主要目的是預先設置函數的一些參數,這樣在使用 new 進行初始化時就能夠只傳入其他的參數。bind(..) 的功能之一就是能夠把除了第一個 參數(第一個參數用於綁定 this)以外的其餘參數都傳給下層的函數(這種技術稱爲「部 分應用」,是「柯里化」的一種)。舉例來講:
function foo(p1,p2) { this.val = p1 + p2; } // 之因此使用 null 是由於在本例中咱們並不關心硬綁定的 this 是什麼 // 反正使用 new 時 this 會被修改 var bar = foo.bind( null, "p1" ); var baz = new bar( "p2" ); baz.val; // p1p2
咱們能夠根據優先級來判斷函數在某個調用位置應用的是哪條規則。能夠按照下面的 順序來進行判斷:
(數值越小優先級越高)
1 函數是否在 new 中調用(new 綁定)?若是是的話 this 綁定的是新建立的對象。
var bar = new foo()
2 函數是否經過 call、apply(顯式綁定)或者硬綁定調用?若是是的話,this 綁定的是 指定的對象。
var bar = foo.call(obj2)
3 函數是否在某個上下文對象中調用(隱式綁定)?若是是的話,this 綁定的是那個上 下文對象。
var bar = obj1.foo()
4 若是都不是的話,使用默認綁定。若是在嚴格模式下,就綁定到 undefined,不然綁定到 全局對象。
var bar = foo()
在某些場景下 this 的綁定行爲會出乎意料,你認爲應當應用其餘綁定規則時,實際上應用 的多是默認綁定規則。
若是你把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind,這些值 在調用時會被忽略,實際應用的是默認綁定規則.
另外一個須要注意的是,你有可能(有意或者無心地)建立一個函數的「間接引用」,在這 種狀況下,調用這個函數會應用默認綁定規則。
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 (p.foo = o.foo)(); // 2
賦值表達式 p.foo = o.foo 的返回值是目標函數的引用,所以調用位置是 foo() 而不是 p.foo() 或者 o.foo()。
1
用硬綁定以後就沒法使用隱式綁定或者顯式綁定來修改 this。
若是能夠給默認綁定指定一個全局對象和 undefined 之外的值,那就能夠實現和硬綁定相 同的效果,同時保留隱式綁定或者顯式綁定修改 this 的能力。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) { var fn = this; // 捕獲全部 curried 參數 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; }; }
箭頭函數並非使用 function 關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定 義的。箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決 定 this。
以前咱們有說過setTimeout的this綁定丟失的問題es6和es6之前的解決辦法以下,能夠比較着看
es6:
function foo() { setTimeout(() => { // 這裏的 this 在此法上繼承自 foo() console.log( this.a ); },100); } var obj = { a:2 }; foo.call( obj ); // 2
es5
function foo() { var self = this; // lexical capture of this setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2
ES6 中的箭頭函數並不會使用四條標準的綁定規則,而是根據當前的詞法做用域來決定 this,具體來講,箭頭函數會繼承外層函數調用的 this 綁定(不管 this 綁定到什麼)。這 其實和 ES6 以前代碼中的 self = this 機制同樣。