this
是JavaScript的關鍵字,它最初應該是從Java、C++等面向對象的語言中借鑑來的。javascript
好比,在Java中沒有函數只有方法,this只能用在類的成員方法或構造方法中,表示當前實例對象。因此在Java中this的含義很明確,在其餘語言中也相似。java
然而到了JavaScript中,this變得複雜了起來:不只函數內能夠用,在全部函數外(全局上下文中)也能夠用;函數中的this的含義在函數聲明時沒法肯定,要到運行期才能肯定,並且與調用函數的方式有關;代碼是不是嚴格模式也會影響this的取值。git
在ES5.1中,有一個所謂執行上下文(Execution Context,EC)的概念,簡單的說就是JS引擎的執行進入到某塊代碼區域時,爲該代碼區域創建的上下文對象,主要用來記錄該區域中聲明的變量、函數等。github
EC有三個重要組成部分:VE
、LE
和ThisBinding
。前兩個是詞法環境,暫且無論。第三個 ThisBinding
就是指在該代碼區域的this的值。編程
可見,this是跟某塊代碼區域關聯的。而在JS中,代碼區域有三種:app
global代碼框架
function代碼函數
eval代碼。this
此文中主要討論前兩種。es5
全局代碼區域是全部函數以外的區域。在此區域中的this就是指全局對象window
(在Node.js中是global
)。
示例:
var num = 123; console.log(this.num); // 輸出123
參考:http://es5.github.io/#x10.4.1.1
函數代碼區域是指某個函數內的代碼,可是不包括它所嵌套的函數內的代碼。從咱們能夠看出:
this是與包裹它的且離它最近的函數相關的,this既不能穿透到外部的函數,也不能穿透進內部的函數。
示例:
btn.addListener('click', function() { var that = this; dosth(function() { console.log(that.name); }); });
一般每一個函數中的this是不一樣的,內部函數能夠引用外部函數的局部變量,可是不能直接引用外部函數的this。經過將外部函數的this賦值給一個局部變量能夠解決這個問題。
函數內的this的具體函數比較複雜,主要與調用這個函數的方式有關。主要包括如下狀況:
示例:
var num = 123; function fn() { console.log(this.num); } function fn2() { "use strict" console.log(this.num); } fn(); // 輸出123 fn2(); // 報錯
直接調用函數時,若是是在嚴格模式下,this會被設爲undefined
;若是是在非嚴格模式下,this會被設爲全局對象window
。
示例:
var student = { name: 'Tom', sayName: function() { console.log(this.name); }; }; student.sayName(); // 輸出Tom
做爲方法調用時,this指方法所屬的對象。
參考:http://es5.github.io/#x10.4.3和 http://es5.github.io/#x11.2.3
除了上述兩種固定的狀況外,Javascript提供了一種能夠爲所欲爲地根據須要更改函數中this方法。即便用函數對象的call
或apply
方法來調用函數,顯然這種方式給編程帶來了極大的靈活性。
示例:
function fn() { var args = Array. prototype. slice.call(arguments, 1); console.log(args); } fn(1, 2, 3); // 輸出[2, 3]
這種方法經常使用的場景就是:把一個對象的方法"借"給另外一個具備相似結構的對象使用。
與call和apply不一樣,bind方法是在調用前就把函數內的this綁定了,並且一旦綁定就不能再改變。實際上bind方法返回了一個原函數的新版本。
示例:
function fn() { console.log(this.age); } var fn2 = fn.bind({age: 18}); fn2() // 輸出18 fn2.call({age: 25}) // 輸出18
經過bind獲得的函數,不論用哪一種方式調用,它的this都是相同的。
參考:http://es5.github.io/#x15.3.4.5
小結:函數中的this是由調用函數的方式決定的。同一個函數,調用它的方式不一樣,那麼它內部的this就可能不一樣。換句話說,this是動態決定的。
當構造函數經過new操做符來調用時,this表示正在建立的對象。
示例:
function Person(name, age) { this.name = name; this.age = age; } var jerry = new Person('Jerry', 12); console.log(jerry.age); // 輸出12
正由於這個緣由,咱們能夠在構造函數中經過this
給實例添加屬性。
參考:http://es5.github.io/#x11.2.2
回調函數也只不過是函數的一種,實際上這種狀況已經包含在了前面提到的狀況中。可是因爲回調函數的調用者每每不是咱們本身,而是回調函數的接收者,即某個庫或框架、甚至是JS運行時環境。這樣一來,回調函數在中的this是什麼就與對方的調用方式有關了,所以變得比較複雜,因此單獨拿出來討論一下。
狀況1:沒有明確做用對象的狀況下,一般this爲全局對象
例如setTimeout
函數的回調函數,它的this就是全局對象。你若是但願本身指定this,能夠經過bind函數等方法。
狀況2:某個事件的監聽器回調函數,一般this就是事件源對象
例如:
button.addEventListener('click', fn)
fn的中的this就是事件源button對象。
狀況3:某些API會專門提供一個參數,用來指定回調函數中的this
例如,咱們能夠從新設計一個能夠指定this的setTimeout:
function setTimeoutExt(cb, period, thisArg) { setTimeout(function() { cb.call(thisArg); }, period); }
另外,在ExtJS中也大量使用了能夠指定this的接口。
(代補充)
this,除了面嚮對象語言中通用的那兩種狀況(方法和構造函數)外,在JavaScript 中還提供了更多的使用方式,雖然這讓JS中的this變得相對難以掌握,可是它使得JS更加豐富更加靈活。咱們能夠把this當作函數的一個特殊的隱含的參數,這個參數表明函數正在操做的主體。
注:時間比較倉促,有些地方沒有太深刻,代碼實例也比較簡單。有機會繼續完善。