# 版本二 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 ``` ![](https://i.imgur.com/oLDCsfU.png) 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://i.imgur.com/FaVQbXX.png) [參考連結一](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对象 ![](https://i.imgur.com/2fHL7g3.png) * 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(); ```