# `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