# 版本二 Javascript this
this是什麼?簡單來說this是指目前的物件,
那目前的物件是如何指定的?this會經由你呼叫函數的方式來指定。
this值是何時產生的?函式呼叫執行時產生。
### 什麼是 前後文本(context)? 也被稱作this Context
字面上很像"執行上下文(Execution Context“但它不是EC,概念上很像作用域Scope,但它也不是Scope。
Context(上下文)指的是函式在被呼叫執行時,所處的物件環境。
在程式語言中的Context指的是物件的環境之中,也就是處於物件所能提供的資料組合中,這個Context是由this值來提供。
## 在全域環境下的this (Global context)
this在全域執行環境下,會被當作全域物件。
```*
console.log(this)
// Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
// 在網路瀏覽器中,window 物件也是全域物件。
console.log(this === window); // true
this.a=37;
console.log(window.a);
//37
```
## 函式環境下的this (Function context)
在函式內的 this 值取決於該函式如何被呼叫。
#### this 與前後文本(context)綁定基本原則
1. 預設綁定(Default Binding) ->直接呼叫funciton
2. 隱含式綁定(Implicit Binding) ->透過物件指定呼叫函示
3. 顯式綁定(Explicit Binding)->call(),apply(),bind()
4. new 關鍵字綁定->new
* 特立獨行的箭頭函式
## 一般的函式呼叫中的this /預設綁定(Default Binding)
this會指向全域物件或(嚴格模式)undefined
```*
function f1(){
console.log(this)
}
f1() // Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
```
因為 f2 是直接被呼叫,**沒有特別設定參考的物件,這裡的this就會參考全域物件。**
補充:另一種情況是當你綁定的對象是null或是undefined,this會自動指定到全域物件。
```*
f1.call(null)
//Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
```
> 「脫離了物件,this 的值就沒什麼意義」
## 物件呼叫函式中的this /隱含式綁定(Implicit Binding)
呼叫的物件不同,所以執行的結果不同
```*
function f1(){
console.log(this.a)
}
let obj1={a:2,func:f1};
obj1.func()
//2 //this =>{a:2,func:f1}
let func2=obj1.func
func2();
//undefined //this =>window
```
**決定this的關鍵不在於它屬於哪個物件,而是在於function呼叫的時機點**,
透過在物件上使用點"."來呼叫它,這物件就成為「this」。
### 巢狀迴圈中的this,分界?
```*
const obj = { a: 1 };
function outter() {
function inner() {
console.log(this);
}
inner();
}
outter.call(obj);
```
內部的inner不知道this是什麼?
那是因為this是以函式怎麼被呼叫作為區分,這邊的inner是直接呼叫,所以this會指向window。
> 對this值來說,它根本不關心函式是在哪裡定義或是怎麼定義的,它只關心是誰呼叫了它。
<br/>
#### 那要怎麼樣可以讓inner函式指到this的值呢?
### 可以透過建立另一個變數that作為this的參考
```*
const obj = { a: 1 };
function outter() {
//暫存outter的this值
const that = this;
function inner() {
console.log(that); //用作用域鏈讀取outter中的that值
}
inner();
}
outter.call(obj); //Object {a: 1}
```
that只是一個變數,為了暫時保存在outter函式被呼叫時的this值用的,讓this可以傳遞到inner函式之中。
> this 的值跟作用域跟程式碼的位置在哪裡完全無關,只跟「你如何呼叫」有關
## 強制指定函式的this /顯式綁定(Explicit Binding)
可以用call(),apply(),bind(),指定函示呼叫時this指向的物件。
* call(呼叫): 以個別提供的this代表值與傳入參數值來呼叫函式。
* apply(應用): 與call方法功能一樣,只是除了this值傳入外,另一個傳入參數值使用陣列。
* bind(綁定): **建立一個新的函式**,這個新函式呼叫前就先綁定this指定的物件,在呼叫時,會以提供的this值與參數值來進行呼叫。
那像是上面that的例子,我們可以改用這幾個方式去綁定this.
bind():
```*
const obj = { a: 1 };
function outter() {
function inner() {
console.log(this);
}
inner.bind(this)(); //bind是創建新的函式,所以要在呼叫();
}
outter.call(obj); //Object {a: 1}
```
call()
```*
const obj = { a: 1 };
function outter() {
function inner() {
console.log(this);
}
inner.call(this);
}
outter.call(obj); //Object {a: 1}
```
補充:bind vs call :被bind綁定過後值不會被call改變
```*
const obj1 = { a: 1 };
function outter() {
function inner() {
console.log(this);
}
inner.bind(this)();
}
let newFunc = outter.bind(obj1); //newFunc綁定 obj
newFunc(); //{a: 1}
const obj2 = { a: 2 }; //另一個obj2
newFunc.call(obj2); //{a: 1} //嘗試呼叫newFunc用call綁定obj2
//結果沒有被改變喔~因為已經bind obj1
```
## new 關鍵字綁定
### 建構子函式(constructor function)/函式建構式(function constructor)
#### 用一般函式建立物件
```*
//用一般函式建立物件
function funcPerson(firstName) {
let obj = {};
obj.firstName = firstName;
obj.sayhi = function () {
console.log("hi! ", this.firstName);
};
return obj;
}
//呼叫後可以建立新的人
const funcPerson1 = funcPerson("Sol");
console.log(funcPerson1.firstName); //Sol
funcPerson1.sayhi(); //Hi! Sol
```
#### 使用建構子函式建立物件
* 使用建構子創立了兩個不同的物件,這兩個物件均完整封包,不致與其他功能衝突;但具備相同的屬性firstName與函式sayhi()。透過「this」確保物件可使用自己的值而不致混淆其他數值。
* new的作用:「new」關鍵字告知瀏覽器「我們要建立新的物件實例」
* 建構子函式可讓你有效率建立所需物件,並依需要為其添加資料與函式。在新的物件實例透過建構子函式產生後,**其核心將透過一種稱為原型鏈(Prototype chain)的參照鏈連在一起。**
* 補充:建構子函式名稱往往以大寫字母起頭,如此可方便你在程式碼中找出建構子函式。
```*
//若使用建構子函式,方便很多,不需要自己建立一個空物件回傳
function Person(firstName) {
this.firstName = firstName;
this.sayhi = function () {
console.log("hi! ", this.firstName);
};
}
const person1 = new Person("Chi");
const person2 = new Person("Tom");
console.log(person1.firstName); //Chi
person1.sayhi(); //hi! Chi
console.log(person2.firstName); //Tom
person2.sayhi(); //hi! Tom
```

1. new 這個關鍵字其實是眾多**運算子**(operators)其中之一。
2. new,他的作用是產生一個**全新的空物件**,然後新建的物件會被設定為那個function的**this**。
3. 建構子**預設**透過 this 回傳該物件的參照
4. 但它其實也能回傳其他物件,如果回傳的是值不是物件的話,就會回傳 this 這個物件。如果回傳的是物件的話,會回傳物件。
```
function Person(firstName) {
...
}
const person1 = new Person("Chi");
//new ------------------------
====> let person1={} (2)
====> Person.call(person1,"Chi") (3)
```
第四點範例:
```*
function Person(firstName) {
this.firstName = firstName;
this.sayhi = function () {
console.log("hi! ", this.firstName);
};
return {
firstName: "我要取代你",
sayhi: function () {
console.log("hi! ", this.firstName);
},
};
}
const person1 = new Person("Chi");
console.log(person1.firstName); //我要取代你
person1.sayhi(); //hi! 我要取代你
```
## 箭頭函式
1. 自己沒有this,強制綁定在宣告它的地方的this。
2. 無論用上上方綁定的方法都沒辦法改變this的內容,也不能作為建構子來使用。
3. 箭頭函式遵循常規變量查找規則。因此,如果在當前範圍中搜索不到 this 變量時,他們最終會尋找其封閉範圍。
(找到上層有this的地方)
```*
const obj = { a: 1 };
function outter() {
/////this => obj->{a:1}
// 這邊印出來的 this 是什麼,inner 的 this 就是什麼
const inner = funciotn() {
console.log(this);
};
////this => obj->{a:1}
inner();
}
outter.call(obj); //Object {a: 1}
```
```*
var name = '全域阿婆'
var auntie = {
name: '漂亮阿姨',
callName: function () {
// 注意,這裡是 function,以此為基準產生一個作用域
console.log('1', this.name); // 1 漂亮阿姨
setTimeout(() => {
console.log('2', this.name); // 2 漂亮阿姨
console.log('3', this); // 3 auntie 這個物件
}, 10);
},
callName2: () => {
// 注意,如果使用箭頭函式,this 依然指向 window
console.log('4', this.name); // 4 全域阿婆
setTimeout(() => {
console.log('5', this.name); // 5 全域阿婆
console.log('6', this); // 6 window 物件
}, 10);
}
}
auntie.callName();
auntie.callName2();
```
#### this 在 Arrow function 中是被綁定的,所以套用 apply, call, bind 的方法時是無法修改 this。
```*
let family = {
brother: "小明",
func3: () => {
console.log(this);
},
};
const func = () => {
console.log(this);
};
const func2 = function () {
console.log(this);
};
func.call(family); // window
func.bind(family)(); //this 依然是 window
family.func3(); //this 是 window 找上一層所以是window
func2.call(family); // 一般函示 this 則是傳入的物件 //{brother: "小明", func3: ƒ}
```
#### 不能用在建構式
由於 this 的是在物件下建立,所以箭頭函式不能像 function 一樣作為建構式的函式,如果嘗試使用此方法則會出現錯誤 (... is not a constructor)。
```*
const familyMember = (brother, sister) => {
this.brother = brother;
this.sister = sister;
};
const myfamily = new familyMember("小明", "小花");
// Uncaught TypeError: familyMember is not a constructor
```
### 綜合練習:
```*
var obj1 = {
value: "hi",
print: function () {
console.log(this);
},
arrow: () => {
console.log(this);
},
};
var obj2 = { value: 17 };
obj1.print();
obj1.print.call(obj2);
new obj1.print();
obj1.arrow();
obj1.arrow.call(obj2);
new obj1.arrow();
```
參考連結:
[重新介紹 JavaScript](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/A_re-introduction_to_JavaScript)
[從ES6開始的JavaScript學習生活
-this](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/this.html)
[MDN-this](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/this)
[淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂](https://blog.techbridge.cc/2019/02/23/javascript-this/)
[箭頭函式 (Arrow functions)](https://wcc723.github.io/javascript/2017/12/21/javascript-es6-arrow-function/#%E7%B0%A1%E7%9F%AD%E7%9A%84%E8%AA%9E%E6%B3%95)
008天重新認識javascript
[初學者應知道的物件導向 JavaScript](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Object-oriented_JS)
[[筆記] 談談 JavaScript 中的 function constructor 和關鍵字 new](https://pjchender.blogspot.com/2016/06/javascriptfunction-constructornew.html)
# (舊版)JavaScript中的this。
* **this 是函式內的關鍵字**
* **this 是function執行時會生成的一個內部物件。**
* **隨著function執行場合不同,this所指的值也不同。**
<br>
## 在调用函数时使用new关键字,函数内的this是一个全新的对象。
1. 創建新的物件並將他綁到this keyword 上。
2. 將這個空對象的原型,指向建構函數的prototype屬性。
(原型串鏈) this.__ proto __=constructor.prototype; (建構子原型)
3. 他會跑一些邏輯,那如果函數沒有return其他的物件類型對象,那他就會return this這個新物件。
```
// this = {};
// this.__proto__(原型串鏈) = Constructor.prototype[建構子原型位置];
// Set up logic such that: if
// there is a return statement
// in the function body that
// returns anything EXCEPT an
// object, array, or function:
// return 'this' (the newly
// constructed object)
// instead of that item at
// the return statement;
// return this;
```
```
function foo(a) {
console.log(this); //foo{}
this.a = a;
console.log(this); //foo{a:222}
}
let obj = new foo(222);
console.log(obj.a) //222;
///////////
function foo(a) {
console.log(this); //foo{}
this.a = a;
console.log(this); //foo{a:222}
let ab = { hi: "there" };
return ab;
}
let obj = new foo(222);
console.log(obj); //{hi: "there"}
console.log(obj.a);//undefined
console.log(obj.__proto__ === foo.prototype); //ture
console.log(obj.constructor === foo);//ture
///////////
function foo(a) {
console.log(this); //foo{}
this.a = a;
console.log(this); //foo{a:222}
}
foo.prototype.c = "c";
let obj = new foo(222);
console.log(obj); //"foo{a:222}"
console.log(obj.a); //"222"
console.log(obj.c); //"c"
```

[參考連結一](https://www.notion.so/This-4863fd528a044ebd8cfb8d68f2f2ecc5#5a1877a1c42c464dbae848460550d0e8)
[參考連結二](https://www.notion.so/This-4863fd528a044ebd8cfb8d68f2f2ecc5#b0b57986627a4e439031b3c8c740bfcf)
<br>
## 如果apply、call或bind方法用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。
### apply、call或bind 是可以強制指定 this的方法
* call、apply→回傳function執行結果
強定指定某個物件作為該function的this,
二者差別在於傳入的參數的方式不同
apply(thisArg, argsArray)
call(thisArg, arg1, ... , argN)
* bind→回傳的是綁定 this 後的原函數
```
function fn() {
console.log(this);
}
let obj = {
value: 5
};
let boundFn = fn.bind(obj);
boundFn(); // -> { value: 5 }
fn.call(obj); // -> { value: 5 }
fn.apply(obj); // -> { value: 5 }
```
<br>
## 当函数作为对象里的方法被调用时,函数内的this是调用该函数的对象。比如当obj.method()被调用时,函数内的 this 将绑定到obj对象

* this代表function執行時所屬的物件
* this 會根據呼叫的物件不同,執行的結果也會不同。根據呼叫時,點"."左邊的物件
```
const print = function () {
console.log(this);
};
let obj1 = {
value: 5,
print: print,
};
let obj2 = {
value: 3,
print: print,
};
obj1.print(); //{value: 5, print: ƒ}
obj2.print(); // {value: 3, print: ƒ}
```
### 如果调用函数不符合上述规则,那么this的值指向全局对象(global object)。浏览器环境下this的值指向window对象,但是在严格模式下('use strict'),this的值为undefined。
<br>
* 普通的函式呼叫this 會指向window 或是undefined ,根據有沒有用 ‘’use strict ''嚴格模式
```
"use strict";
console.log("Global this -----", this);
//Window {window: Window, self: Window, document: document, name: "", location: Location, …}
const setName = function (name) {
console.log(name); //Sol
console.log(this); //undefined || Window {window: Window, self: Window, document: document, name: "", location: Location, …}
};
setName("Sol");
```
```
function func() {
console.log(this.a);
}
var obj = {
a: 2,
foo: func,
};
obj.foo();
let func2 = obj.foo;
func2();
```
<br>
## 如果符合上述多个规则,则较高的规则(1 号最高,4 号最低)将决定this的值。
1. 如果function 是透過 New 關鍵字 → this就是被建構出來的物件
2. 如果 function 是透過.call .apply .bind → this 就是被指定的物件
3. 如果function 是被物件呼叫→ this就是該物件
4. 如果沒有滿足以上條件 直接呼叫funciton → this 會window 或undefined 根據是不是在嚴格模式下。
```*
var obj1 = {
value: "hi",
print: function () {
console.log(this);
},
};
var obj2 = { value: 17 };
obj1.print();
obj1.print.call(obj2); // -> { value: 17 }
new obj1.print(); // -> {}
```
<br>
## 如果该函数是 ES2015 中的箭头函数,将忽略上面的所有规则,this被设置为它被创建时的上下文。
* 因為他沒有自己的this所以他參考的parent scope
```
const setNameArrow = (name) => {
console.log(name); //Sol
console.log(this); //window
};
setNameArrow("Kate");
const iAmObj = {
hi: "there",
prove: function () {
console.log(this); //{hi: "there", prove: ƒ}
const setNameArrow = (name) => {
console.log(name); //Kate
console.log(this); //{hi: "there", prove: ƒ}
};
setNameArrow("Kate");
},
};
iAmObj.prove();
```
```
var value = "wahaha";
const iAmObj2 = {
value: "SOl",
age: 23,
setNameArrow: () => {
console.log("Arrow inside obj-----", this.value);
console.log("Arrow inside obj-----", this);
},
};
iAmObj2.setNameArrow();
```