http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/javascript
this關鍵字對於javascript初學者,即使是老手可能都是一個比較容易搞暈的東西。本文試圖理順這個問題。java
實際上js中的this和咱們天然語言中的代詞有相似性。好比英語中咱們寫"John is running fast because he is trying to catch the train"node
注意上面的代詞"he",咱們固然能夠這樣寫:"John is running fast because John is trying to catch the train" ,這種狀況下咱們沒有使用this去重用代替John。jquery
在js中this關鍵字就是一個引用的shortcut,他指代一個object,是執行的代碼body的context環境。看下面的代碼:web
var person = { firstName: "Penelope", lastName: "Barrymore", fullName: function () { // Notice we use "this" just as we used "he" in the example sentence earlier?: console.log(this.firstName + " " + this.lastName); // We could have also written this: console.log(person.firstName + " " + person.lastName); } }
若是咱們使用person.firstName,person.lastName的話,咱們的代碼可能會產生歧義。好比,若是有一個全局變量,名字就是person,那麼person.firstName可能試圖從全局的person變量來訪問其屬性,這可能致使錯誤,而且難以調試。所以,咱們使用this關鍵字,不只爲了代碼更美(做爲以個referent),並且爲了更加精確而不會產生歧義。相似於剛剛舉例的天然語言中,由於代詞"he"使得句意更清晰,由於更加清晰地代表咱們正在引用一個特定的John這我的。ajax
正如代詞"he"用於引用存於context中無歧義的名詞同樣,this關鍵字用於引用一個對象,而這個對象就是function(在該函數中,使用了this指針)所綁定的對象.(the this keyword is similarly used to refer to an object that the function(where this is used) is bound to.) tjson
在js中,全部的函數體(function body)都能訪問this關鍵字. this keyword就是函數執行的context.默認狀況下,this引用着調用該函數的那個對象(也就是thisObj.thisFunction中的thisObj)(this is a reference to the object on which a particular function is called),在js中,全部的函數都會被綁定到一個object.然而,咱們可使用call(), apply()來在運行時變動這種binding關係。瀏覽器
首先,咱們須要澄清的是:js中全部的函數實際上都是"methods".也就是說全部的function都是某個對象的屬性。雖然咱們能夠定義看起來像是獨立自由的函數,可是實際上這時候,這些含糊被隱式地被建立爲window object的屬性properties(在node環境中是其餘的object,多是process)。這也就意味着,即便咱們建立一個自由獨立的function而沒有顯然指定其context,咱們也能夠以window屬性的方式來調用該函數。閉包
// Define the free-floating function. function someMethod(){ ... } // Access it as a property of Window. window.someMethod();
既然咱們知道了js的全部函數度做爲一個對象的屬性而存在,咱們能夠具體探討下執行上下文的默認綁定這個概念了。默認狀況下,一個js函數在該函數所屬的對象的上下文中執行(js function is executed in the context of the object for which it is a property).也就是說,在函數body中,this關鍵詞就是對父親對象的引用,看看下面的代碼:
// "this" keyword within the sayHello() method is a reference to the sarah object sarah.sayHello(); // "this" keyword within the getScreenResolution() function is a reference to the window object (since unbound functions are implicitly bound to the global scope) getScreenResolution();
以上是默認的狀況,除此以外,js提供了一種變動任何一個method的執行上下文的機制: call()或者apply().這兩個函數都能用於綁定"this"關鍵字到一個明確的context(object)上去。
method.call( newThisContext, Param1, ..., Param N )
method.apply( newThisContext, [ Param1, ..., Param N ] );
再看一個複雜一點的代碼案例:
<!DOCTYPE html> <html> <head> <title>Changing Execution Context In JavaScript</title> <script type="text/javascript"> // Create a global variable for context (this lives in the // global scope - window). var context = "Global (ie. window)"; // Create an object. var objectA = { context: "Object A" }; // Create another object. var objectB = { context: "Object B" }; // -------------------------------------------------- // // -------------------------------------------------- // // Define a function that uses an argument AND a reference // to this THIS scope. We will be invoking this function // using a variety of approaches. function testContext( approach ){ console.log( approach, "==> THIS ==>", this.context ); } // -------------------------------------------------- // // -------------------------------------------------- // // Invoke the unbound method with standard invocation. testContext( "testContext()" ); // Invoke it in the context of Object A using call(). testContext.call( objectA, ".call( objectA )" ); // Invoke it in the context of Object B using apply(). testContext.apply( objectB, [ ".apply( objectB )" ] ); // -------------------------------------------------- // // -------------------------------------------------- // // Now, let's set the test method as an actual property // of the object A. objectA.testContext = testContext; // -------------------------------------------------- // // -------------------------------------------------- // // Invoke it as a property of object A. objectA.testContext( "objectA.testContext()" ); // Invoke it in the context of Object B using call. objectA.testContext.call( objectB, "objectA.testContext.call( objectB )" ); // Invoke it in the context of Window using apply. objectA.testContext.apply( window, [ "objectA.testContext.apply( window )" ] ); </script> </head> <body> <!-- Left intentionally blank. --> </body> </html>
以上代碼的輸出以下:
testContext() ==> THIS ==> Global (ie. window) .call( objectA ) ==> THIS ==> Object A .apply( objectB ) ==> THIS ==> Object B objectA.testContext() ==> THIS ==> Object A objectA.testContext.call( objectB ) ==> THIS ==> Object B objectA.testContext.apply( window ) ==> THIS ==> Global (ie. window)
(function () { "use strict"; this.foo = "bar"; // *this* is undefined, why? }()); function myConstructor() { this.a = 'foo'; this.b = 'bar'; } myInstance = new myConstructor(); // all cool, all fine. a and b were created in a new local object // 若是是strict mode, 則顯示 "TypeError: this is undefined" myBadInstance = myConstructor(); // oh my gosh, we just created a, and b on the window object
在js中有一種所謂沙盒模型"boxing" mechanism. 這個盒子在進入被調用函數執行上下文以前將包裹或者變動this object.在匿名函數中,因爲在strict mode下並未以obj.method方式來調用匿名函數,所以this就爲undefined(緣由是匿名函數就是一個閉包,其做用就是隔離了global scope,所以不會默認到window.method上去).而在非strict mode下則this指向window.
function Request(destination, stay_open) { this.state = "ready"; this.xhr = null; this.destination = destination; this.stay_open = stay_open; this.open = function(data) { this.xhr = $.ajax({ url: destination, success: this.handle_response, error: this.handle_failure, timeout: 100000000, data: data, dataType: 'json', }); }; /* snip... */ } Request.prototype.start = function() { if( this.stay_open == true ) { this.open({msg: 'listen'}); } else { } }; var o = new Request(destination, stay_open); o.start()
this
object is not set based on declaration, but by invocation. What it means is that if you assign the function property to a variable like x = o.start
and call x()
, this
inside start no longer refers to o
var o = new Request(...); setTimeout(function() { o.start(); }, 1000);
首先,須要知道的是js中的全部函數都有peroperties,就像objects都有properties同樣,由於function自己也是一個object對象。this能夠看做是this-functionObj的屬性,而且只有當該函數執行時,該函數對象的this屬性將會被賦值,"it gets the this property ---- a variable with the value of the object that invokes the function where this is used"
this老是指向或者說引用(幷包含了對應的value)一個對象,而且this每每在一個function或者說method中來使用。注意:雖然在global scope中咱們能夠不在function body中使用this,而是直接在global scope中使用this(實際上指向了window),可是若是咱們在strict mode的話,在global function中,或者說沒有綁定任何object的匿名函數中,若是使用this, 那麼這個this將是undefined值.
假設this在一個function A中被使用,那麼this就將引用着調用 function A的那個對象。咱們須要這個this來訪問調用function A對象的method和property.特別地,有些狀況下咱們不知道調用者對象的名稱,甚至有時候調用者對象根本沒有名字,這時就必須用this關鍵字了!
var person = { firstName :"Penelope", lastName :"Barrymore", // Since the "this" keyword is used inside the showFullName method below, and the showFullName method is defined on the person object, // "this" will have the value of the person object because the person object will invoke showFullName () showFullName:function () { console.log (this.firstName + " " + this.lastName); } } person.showFullName (); // Penelope Barrymore
再看一個jquery事件處理函數中使用this關鍵字的常見例子:
// A very common piece of jQuery code $ ("button").click (function (event) { // $(this) will have the value of the button ($("button")) object // because the button object invokes the click () method, this指向button console.log ($ (this).prop ("name")); });
The use of $(this), which is jQuery’s syntax for the this keyword in JavaScript, is used inside an anonymous function, and the anonymous function is executed in the button’s click () method. The reason $(this) is bound to the button object is because the jQuery library binds$(this) to the object that invokes the click method. Therefore, $(this) will have the value of the jQuery button ($(「button」)) object, even though $(this) is defined inside an anonymous function that cannot itself access the 「this」 variable on the outer function.
咱們先拋出一個心法: this不會有value,直到一個object invoke了這個函數(this在這個函數中使用).爲了行文方便,咱們將使用this關鍵字的函數稱爲thisFunction.
雖然默認狀況下,this都會引用定義了this(也就是有this引用)的對象,可是隻到一個對象調用了thisFunction,這個this指針纔會被賦值。而這個this value只決定於調用了thisFunction的對象。儘管默認狀況下this的值就是invoking ojbect(xxObj.thisFunction),可是咱們也能夠經過xxObj.thisFunction.call(yyObj,parameters), apply()等方式來修改this的默認值!~
在global scope中,當代碼在瀏覽器中執行時,全部的全局variable和function都被定義在window object上,所以,在一個全局函數中當使用this時,this是引用了全局的window對象的(注意必須是非stric mode哦),而window對象則是整個js應用或者說web page的容器
有如下幾個場景,this會變得很是易於使人誤解:
1.當咱們借用一個使用了this的方法method;
2.當咱們將使用了this的method給到一個變量時;
3.當一個使用了this的函數被做爲回調函數參數時;
4.當在一個閉包closure裏面的函數中使用this時
下面咱們將一一探討這些狀況下this的正確取值是什麼
javascript中context的概念和天然語言中的主語有相似性。「John is the winner who returned the money」.本句的主語是John, 咱們能夠說本劇的context上下文就是John,由於本句此時的焦點就在他身上,甚至who這個代詞指代的也是前面的這個主語John.正如咱們能夠經過使用一個分號 " ; " 來更改句子的主語同樣,咱們也能夠經過使用另一個object來調用這個function,從而改變context.
var person = { firstName :"Penelope", lastName :"Barrymore", showFullName:function () { // The "context" console.log (this.firstName + " " + this.lastName); } } // The "context", when invoking showFullName, is the person object, when we invoke the showFullName () method on the person object. // And the use of "this" inside the showFullName() method has the value of the person object, person.showFullName (); // Penelope Barrymore // If we invoke showFullName with a different object: var anotherPerson = { firstName :"Rohit", lastName :"Khan" }; // We can use the apply method to set the "this" value explicitly—more on the apply () method later. // "this" gets the value of whichever object invokes the "this" Function, hence: person.showFullName.apply (anotherPerson); // Rohit Khan // So the context is now anotherPerson because anotherPerson invoked the person.showFullName () method by virtue of using the apply () method
// We have a simple object with a clickHandler method that we want to use when a button on the page is clicked var user = { data:[ {name:"T. Woods", age:37}, {name:"P. Mickelson", age:43} ], clickHandler:function (event) { var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1 // This line is printing a random person's name and age from the data array console.log (this.data[randomNum].name + " " + this.data[randomNum].age); } } // The button is wrapped inside a jQuery $ wrapper, so it is now a jQuery object // And the output will be undefined because there is no data property on the button object $ ("button").click (user.clickHandler); // Cannot read property '0' of undefined
在上面的代碼中,($('button'))本身是一個對象,咱們將user.clickHandler method方法做爲callback函數參數傳入該jquery對象的click()方法中,咱們知道user.clickHandler()中的this再也不指向user對象了。this將指向user.clickMethod運行地所在的對象--由於this在user.clickHandler方法中定義。而invoking這個user.Handler方法的對象則是button object,---user.clickHandler將在button對象的click方法中被執行。
須要說明的是即便咱們經過user.clickHandler()方式來調用(實際上咱們也必須這麼作,由於clickHandler自己就做爲user的一個method來定義的,所以必須這麼去調用), clickHandler()方法也將以button對象做爲上下文去執行,也就是說this如今將指向這個button context對象($('button')).
到這裏,咱們能夠下一個結論:
At this point, it should be apparent that when the context changes—when we execute a method on some other object than where the object was originally defined, the this keyword no longer refers to the original object where 「this」 was originally defined, but it now refers to the object that invokes the method where this was defined.
如何能解決這類問題,而且fix住this指向呢?
在上面的例子中,既然咱們老是但願this.data就是指向到user object的data屬性,咱們可使用Bind(),Apply()或者Call()方法去特別設定this的value.
$ ("button").click (user.clickHandler); // 這個clickHandler this指向button jquery對象 $("button").click (user.clickHandler.bind (user)); // P. Mickelson 43 經過bind指定這個clickHandler中的this就是指user
正如上面說起,當咱們使用一個inner method(a closure)時,this也是很是容易搞混淆的。很是重要一點是:closures閉包不能訪問外部函數(outer function)的this值,由於this變量只能由函數自己來訪問,而不是inner function(的this)
var user = { tournament:"The Masters", data :[ {name:"T. Woods", age:37}, {name:"P. Mickelson", age:43} ], clickHandler:function () { // the use of this.data here is fine, because "this" refers to the user object, and data is a property on the user object. this.data.forEach (function (person) { // But here inside the anonymous function (that we pass to the forEach method), "this" no longer refers to the user object. // This inner function cannot access the outer function's "this" console.log ("What is This referring to? " + this); //[object Window] console.log (person.name + " is playing at " + this.tournament); // T. Woods is playing at undefined // P. Mickelson is playing at undefined }) } } user.clickHandler(); // What is "this" referring to? [object Window]
上面代碼中,因爲匿名函數中的this不能訪問外部函數的this,所以當在非strict mode時,this將綁定到global window對象。
var user = { tournament:"The Masters", data :[ {name:"T. Woods", age:37}, {name:"P. Mickelson", age:43} ], clickHandler:function (event) { // To capture the value of "this" when it refers to the user object, we have to set it to another variable here: // We set the value of "this" to theUserObj variable, so we can use it later var that = theUserObj = this; this.data.forEach (function (person) { // Instead of using this.tournament, we now use theUserObj.tournament console.log (person.name + " is playing at " + theUserObj.tournament); }) } } user.clickHandler(); // T. Woods is playing at The Masters // P. Mickelson is playing at The Masters
3. 當method被賦值給一個變量時,如何fix住this指向
// This data variable is a global variable var data = [ {name:"Samantha", age:12}, {name:"Alexis", age:14} ]; var user = { // this data variable is a property on the user object data :[ {name:"T. Woods", age:37}, {name:"P. Mickelson", age:43} ], showData:function (event) { var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1 // This line is adding a random person from the data array to the text field console.log (this.data[randomNum].name + " " + this.data[randomNum].age); } } // Assign the user.showData to a variable var showUserData = user.showData; // When we execute the showUserData function, the values printed to the console are from the global data array, not from the data array in the user object // showUserData (); // Samantha 12 (from the global data array)
解決方案是特別地經過使用bind方法來指定this值
// Bind the showData method to the user object var showUserData = user.showData.bind (user); // Now we get the value from the user object, because the this keyword is bound to the user object showUserData (); // P. Mickelson 43
在js開發中,借用方法是一個很是常見的實例,做爲js開發者,咱們必定會常常遇到。
// We have two objects. One of them has a method called avg () that the other doesn't have // So we will borrow the (avg()) method var gameController = { scores :[20, 34, 55, 46, 77], avgScore:null, players :[ {name:"Tommy", playerID:987, age:23}, {name:"Pau", playerID:87, age:33} ] } var appController = { scores :[900, 845, 809, 950], avgScore:null, avg :function () { var sumOfScores = this.scores.reduce (function (prev, cur, index, array) { return prev + cur; }); this.avgScore = sumOfScores / this.scores.length; } } //If we run the code below, // the gameController.avgScore property will be set to the average score from the appController object "scores" array // Don't run this code, for it is just for illustration; we want the appController.avgScore to remain null gameController.avgScore = appController.avg();
avg方法的"this"再也不指向gameController object,它將指向到appController對象,由於該avg()方法是在appController對象上執行的。
解決方法:
// Note that we are using the apply () method, so the 2nd argument has to be an array—the arguments to pass to the appController.avg () method. appController.avg.apply (gameController, gameController.scores); // The avgScore property was successfully set on the gameController object, even though we borrowed the avg () method from the appController object console.log (gameController.avgScore); // 46.4 // appController.avgScore is still null; it was not updated, only gameController.avgScore was updated console.log (appController.avgScore); // null
gameController對象借用了appController's avg()方法,在appController.avg()中的this value會被設定爲gameContrller對象,由於咱們使用了apply()方法。
最後,須要牢記:
Always remember that this is assigned the value of the object that invoked the this Function