# `This` ###### tags: `javascript` JavaScript中的`this`比一般C, Java中的`this`還複雜許多,因為在js中this的指向會隨runtime一直在改變的,簡單分類會有五種指向或更改指向方式。 以下的測試環境皆在Google Chrome。 > doku wiki 支援的markdown so 糟糕,所以so醜.... > ## 5種binding方法 ### Default Binding * 套用的時機:甚麼都不做的時後 * 很單純的call一個function的時候,不是以物件的method被call的時候。 * 刻意忽略的時候:使用`call`、`apply`傳入`null`時。 * `this`的值: * strict mode: **`undefined`** * unstrict mode: **global 物件** * Example 1: 非嚴格模式 ```javascript var myString = "global string"; function f ( ) { var myString = "local string"; console.log ( this ); // output: window console.log ( this.myString ); // output: "global string" } f.myString = "attribute string"; f ( ); ``` > `f` 在這邊是被當作一般的function被call的,所以他的`this`就會指向global物件,在網頁端global物件就會是`window`,所以`this.myString`就會是第一行的那個。 > 這邊用了很醜的`var`,可以改成`let`試試看,會有不一樣的結果呢。 > 全域宣告的變數會自動變成global的屬性。 > 試試看: > ```javascript > console.log( window.a ); > > var a = "123"; > console.log ( a ); > console.log ( window.a ); > > let b = "456"; > console.log ( b ); > console.log ( window.b ); > ``` * Example 2: 嚴格模式 ```javascript 'use strict' var myString = "global string"; function f ( ) { var myString = "local string"; console.log ( this ); // output: undefined console.log ( this.myString ); // TypeError: Cannot read property 'myString' of undefined } f.myString = "attribute string"; f ( ); ``` > 因為是嚴格模式,所以`this`就會是`undefined`,再透過他去取值,就會出錯。 ### Implict Binding * 套用的時機:一個function被當作一個物件的method被call的時候。 * `this`的值:該方法是透過哪一個物件被call的就是那個物件。 * Example: ```javascript function f () { console.log ( this.myString ); } function f2 ( func ) { func ( ); } f.myString = "attribute string"; var myString = "global string"; var obj = { func: f, myString: "local string" }; f ( ); // output: "global string" obj.func ( ); // output: "local string" f2 ( obj.func ); // output: "global string" var f3 = obj.func; f3 ( ); // output: "global string" ``` > 第一個output所套用的this綁定就是Default Binding。 > 第二個output,因為是夠過`obj`這個物件去call的,所以`this`的指向就會是`obj`。 > 第三個和第四個output,雖然`func`、`f2`是從`obj`這個物件裡面的值得來,但是最後在call的時候並不是透過obj去call,而是最一般的function call,所以會套用Default Binding(Implicitly Lost)。 ### Explicit Binding * 套用的時機:使用`apply`、`call`、`bind`時。 * `this`的值:傳入時的值。 * `bind`所綁定的`this`不會再被更改,此為*hard binding*,可以看Example 3。 * Example 1: ```javascript function f () { console.log ( this.myString ); } f.myString = "attribute string"; var myString = "global string"; var obj1 = { func: f, myString: "local string 1" }; var obj2 = { func: f, myString: "local string 2" }; obj1.func ( ); // output: "local string1" f.call ( null ); // output: "global string" f.call ( obj2 ) // output: "local string2" var f2 = obj.func; f2.apply ( ob2 ); // output: "local string2" ``` > 第一個output所套用的this綁定就是Implict Binding。 > 第二個output所套用的則是Default Binding > 第三個和第四個output因為是透過`call`和`apply`去做的,所以他們的`this`會是套用Explicit Binding,他們的`this`的值就會是傳入的那個。 * Example 2: ```javascript function f () { console.log ( this.myString ); } f.myString = "attribute string"; var myString = "global string"; var obj = { func: f, myString: "local string" }; let bindFunc = f.bind ( obj ); bindFunc ( ); // output: "local string" ``` > `bindFunc`已經被bind到`obj`上,所以call的時候的`this`就會是`obj`。 * Example 3: ```javascript function f () { console.log ( this.myString ); } f.myString = "attribute string"; var myString = "global string"; var obj = { func: f, myString: "local string1" }; var obj2 = { func: f, myString: "local string2" }; let bindFunc = f.bind ( obj ); bindFunc.apply ( obj2 ); // output: "local string1" ``` > `bindFunc`已經被bind到`obj`上,而且因為是*hard binding*,所以就算使用apply去call,`this`還會是`obj` * Example 4: 自己想啦,Lexical Binding那邊的example有解釋 ```javascript function f () { this.myString = "local string"; } var obj = { myString: "object string" }; var myString = "global string"; console.log ( obj.myString ) // output: ? f.call ( obj ) console.log ( obj.myString ) // output: ? ``` * Example 5: 猜猜看 [ref](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#hard-binding) ```javascript function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a: 2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // output: ? console.log( b ); // output: ? ``` ### `new` Binding * 套用的時機:當function被`new`的時候。 * `this`的值:被創建出來的那個物件。 * 當function被`new`的時候,就是可以把這個function當做一個class,所以它會產生一個物件,這時候,`this`就會是那個物件。就像其他語言,在物件裡面的`this`就會是自己一樣。 * Example : ```javascript function f () { this.myString = "local string"; } f.myString = "attribute string"; var myString = "global string"; let newObj = new f ( ); console.log ( newObj.myString ); // output: "local string" ``` > `newObj`是從`f` `new` 出來的,所以可以把`f`當作一個class,而`newObj`就是`f`的一個instance,這個時候`this`就會是那個`new`出來的物件。 ### Lexical Binding * 套用的時機:使用arrow function的時候。 * `this`的值:看是誰來包這個arrow function那就是誰。 * Example : ```javascript function f () { this.myString = "local string"; return () => { this.myString = "arrow function" } } var obj = { myString: "object string" }; var myString = "global string"; var arrowFunc = f.call ( obj ); console.log ( obj.myString ); // output: "local string" arrowFunc ( ); console.log ( obj.myString ); // output: "arrow function" ``` > 第一個output的結果是套用Explicit Binding,因為`f`是使用`call`被call的,所以`f`在執行的時候的this就會是傳進去的`obj`。所以就會把`obj`的`myString`的值改掉。 > `arrowFunc`會是`f`執行完回傳的結果,回傳了一個arrow function,所以`arrowFunc`現在是一個function,而且還沒有被執行。 > 第二個output。因為前面call了arrowFunc這個function。這個function執行的`this`會套用Lexical Binding,因為包裝這個arrow function的時候的`this`是`obj`,所以在執行這個arrow function的時候的`this`就會是`obj`。所以`obj`的`myString`的值又被改掉了。 ## 各種Binding的優先順序 * Lexical Binding 、`new` Binding > Explicit Binding > Implicit Binding。 * 以下提供各種example,請自行判斷是哪種binding,再根據優先順序得出解答。 * Example 1: ```javascript function f () { console.log ( this.myString ); } var obj = { myString: "object string", func: f }; var obj1 = { myString: "object string1", func: f }; obj.func ( ); // output: ? obj.func.call ( obj1 ); // output: ? ``` * Example 2: ```javascript function f () { this.myString = "function string"; } var obj = { myString: "object string", func: f }; var newObj = new obj.func ( ); console.log ( obj.myString ); // output: ? obj.func ( ); console.log ( obj.myString ); // output: ? console.log ( newObj.myString ); // output: ? ``` * Example 3: ```javascript var counter = 1; function f () { this.myString = `function string ${counter++}`; } var obj = { myString: "object string", func: f }; var bindFunc = f.bind ( obj ); var newObj = new bindFunc ( ); console.log ( obj.myString ); // output: ? console.log ( newObj.myString ); // output: ? obj.func ( ); console.log ( obj.myString ); // output: ? ``` * Example 4: 這個例子有的東西會有Error喔。 ```javascript function f ( ) { return () => { console.log(this.myString); } } var obj1 = { myString: "object string 1" }, var obj2 = { myString: "object string 2" }; var arrowFunc = outer.apply ( obj1 ); arrowFunc(); // output: ? arrowFn.call ( obj2 ); // output: ? new arrowFn(); // error, why? ``` * Example 5: [ref](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#everything-in-order) ```javascript function foo(something) { this.a = something; } var obj1 = { }; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // output: ? var baz = new bar( 3 ); console.log ( obj1.a ); // output: ? console.log ( baz.a ); // output: ? ``` * Example 6: 大雜燴啦,這個例子也有東西會有Error喔。 ```javascript var counter = 1; function f ( ) { this.myString = `local string ${counter++}`; console.log ( this.myString ); return () => this.myString; }; var f2 = ( ) => { this.myString = `local string ${counter++}`; console.log ( this.myString ); return ( ) => this.myString; } var obj1 = { myString: "object string 1", func: f }; var obj2 = { myString: "object string 2", func: f }; var obj3 = { myString: "object string 3", func: f2 }; var bindFunc1 = f.bind ( obj1 ); var bindFunc2 = f2.bind ( obj2 ); var arrowFunc1 = f.apply ( obj1 ); // output: ? var arrowFunc2 = f2.apply ( obj2 ); // output: ? console.log ( obj1.myString ); // output: ? console.log ( obj2.myString ); // output: ? console.log ( arrowFunc1 ( ) ); // output: ? console.log ( arrowFunc2 ( ) ); // output: ? var newObj1 = new arrowFunc1 ( ); // error, why? var newObj2 = new bindFunc1 ( ); // output: ? var newObj3 = new bindFunc2 ( ); // error, why? console.log ( arrowFunc1.apply ( obj3 ) ) // output: ? console.log ( arrowFunc2.apply ( newObj2 ) ) // output: ? var result1 = bindFunc1 ( ); // output: ? var result2 = bindFunc1.call ( newObj2 ) // output: ? var result3 = bindFunc2 ( ); // output: ? var result4 = bindFunc2.call ( obj3 ) // output: ? console.log ( result1 ( ) ) // output: ? console.log ( result2 ( ) ) // output: ? console.log ( result3 ( ) ) // output: ? console.log ( result4 ( ) ) // output: ? console.log ( myString ); // output: ? where does it defined? ``` ## Determining `this` [ref](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#determining-this) Now, we can summarize the rules for determining `this` from a function call's call-site, in their order of precedence. Ask these questions in this order, and stop when the first rule applies. 1. Is the function called with `new` (**new binding**)? If so, `this` is the newly constructed object. `var bar = new foo()` 2. Is the function called with `call` or `apply` (**explicit binding**), even hidden inside a `bind` *hard binding*? If so, `this` is the explicitly specified object. `var bar = foo.call( obj2 )` 3. Is the function called with a context (**implicit binding**), otherwise known as an owning or containing object? If so, `this` is *that* context object. `var bar = obj1.foo()` 4. Otherwise, default the `this` (**default binding**). If in `strict mode`, pick `undefined`, otherwise pick the `global` object. `var bar = foo()` That's it. That's *all it takes* to understand the rules of `this` binding for normal function calls. Well... almost. ## 參考資料 * This (1): https://ithelp.ithome.com.tw/articles/10196074 * This (2): https://ithelp.ithome.com.tw/articles/10196088 * This Priority: https://ithelp.ithome.com.tw/articles/10196282 * this: https://ithelp.ithome.com.tw/articles/10204472 * You Don't Know JS: https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md