1. 程式人生 > >js中this指向的問題與聯絡

js中this指向的問題與聯絡

## 前言 JavaScript 中最大的一個安全問題,也是最令人困惑的一個問題,就是在某些情況下```this```的值是如何確定的。有js基礎的同學面對這個問題基本可以想到:```this```的指向和函式呼叫的方式相關。這當然是正確的,然而,這幾種方式有什麼聯絡嗎?這是我接下來要說明的問題。 ## ```this```從哪裡來 ```this``` 是js的一個關鍵字,和```arguments```類似,它是函式執行時,在函式體內部自動生成的一個物件,**只能在函式體內部使用**。這句話似乎與認知不同,我們在函式體外部即全域性作用域下也能使用```this```。 ```javascript // 直接在全域性作用域下輸出this console.log(this); // 輸出window ``` 但是不要忘記,即便是全域性作用域,依舊是執行在```window```下的,我們寫的程式碼都在```window```的某個函式中。而這也催生了一種理解```this```指向的方法:**```this```永遠指向呼叫者**(非箭頭函式中)。 ## 作為普通函式呼叫 函式作為普通函式直接呼叫(也稱為自執行函式)的時候,無論函式在全域性還是在另一個函式中,```this```都是指向```window```。 ```javascript function fn() { this.author = 'Wango'; } fn(); console.log(author); // Wango ``` 這很好理解,但又不是很好理解,因為在程式碼中省略了```window```,補全後就好理解了:```this```指向的是呼叫者。 ```javascript function fn() { this.author = 'Wango'; } window.fn(); console.log(window.author); // Wango ``` 而在內部函式中,自執行函式中的```this```依舊指向全域性作用域,我們**無法**通過```window.foo()```呼叫函式,但並不妨礙我們先這樣理解(具體參見本文最後一部分[```this```的強制轉型](#md-jump))。 ```javascript function fn() { function foo() { console.log(this); } foo(); // Window window.foo(); // TypeError } fn(); ``` ## 作為建構函式呼叫 在建構函式中,```this```指向```new```生成的新物件,即建構函式是通過```new```呼叫的,建構函式內部的```this```當然就應該指向```new```出來的物件。 ```javascript function Person(name, age) { this.name = name; this.age = age; console.log(this); // Person { name: 'Wango', age: 24 } } new Person('Wango', 24); ``` 建構函式中的```this```與建構函式的返回值型別無關,下列程式碼中```p```指向了建構函式返回的物件,而不是```new```出來的物件。當然,這是建構函式的特性,與本主題關係不大。 ```javascript function Person(name, age) { console.log(this); // Person {} this.name = name; this.age = age; console.log(this); // Person { name: 'Wango', age: 24 } return { name: 'Lily', age: 25 } } Person.prototype.sayName = function() { return this.name + ' ' + this.age } const p = new Person('Wango', 24); console.log(p.sayName()); // TypeError: p.sayName is not a function ``` ## 作為物件方法呼叫 通過物件方法呼叫時,```this```指向應該是最明晰的了。與其他面嚮物件語言的```this```行為相同,指向該方法的呼叫者。 ```javascript function Person(name, age) { this.name = name; this.age = age; } Person.prototype.sayName = fn; function fn() { return this.name + ' ' + this.age } const p = new Person('Wango', 24); console.log(p); // Person { name: 'Wango', age: 24 } console.log(p.sayName()); // Wango 24 ``` ### 通過```[]```呼叫物件方法 通常,我們對於物件方法是通過```.```語法呼叫,但通過```[]```也可以呼叫物件方法,在這種情況下的```this```指向常常會被我們混淆、忽略。 ```javascript function fn() { console.log(this); } const arr = [fn, 1]; arr[0](); // [Function: fn, 1] function fn2() { arguments[0](); } fn2(fn, 1); // [Arguments] { '0': [Function: fn], '1': 1 } ``` 在上例中,無論是陣列還是偽陣列,其本質上都是物件,在通過```[]```獲取函式元素並呼叫的時候,會改變函式中的```this```指向,```this```指向這個陣列或偽陣列,與物件呼叫函式的行為一致。 ## 通過call、apply呼叫 ```javascript function fn() { console.log(this.name); } const author = { name: 'Wango' } fn.call(author); // Wango ``` 這似乎與```this```永遠指向呼叫者相違背,但一旦我們明白了call函式的實現機制就會明白,這不僅不是違背,反而是佐證。對```call```、```apply```、```bind```實現機制不熟悉的同學可以[參考我另一篇文章](https://www.cnblogs.com/hycstar/p/14362676.html),下面擷取```call```簡要說明。 ```javascript // 儲存一個全域性變數作為預設值 const root = this; Function.prototype.myCall = function(context, ...args) { if (typeof context === 'object') { // 如果引數是null,使用全域性變數 context = context || root; } else { // 引數不是物件的建立一個空物件 context = Object.create(null); } // 使用Symbol建立唯一值作為函式名 let fn = Symbol(); context[fn] = this; context[fn](...args); delete context[fn]; } ``` ```call``` 函式最核心的實現在於```context[fn] = this;```和```context[fn](...args);```這兩行。實際上就是將沒有函式呼叫者的普通函式掛載到指定的物件上,這時```this```指向與物件呼叫方法的一致。而```delete context[fn];```是在呼叫後立即解除物件與函式之間的關聯。 ## 嚴格模式下的不同表現 ### ```this```強制轉型 使用函式的```apply()```或```call()```方法時,在非嚴格模式下```null```或```undefined```值會被強制轉型為全域性物件。在嚴格模式下,則始終以指定值作為函式```this```的值,無論指定的是什麼值。這也是為何在嚴格模式下,自執行函式的```this```不再指向```window```,而是指向```undefined```的根本原因。 ```javascript // 定義一個全域性變數 color = "red"; function displayColor() { console.log(this.color); } // 在非嚴格模式下使用call修改this指向,並指定null,或undefined, displayColor.call(null); displayColor.call(); // red // 修改指向無效,傳入null或undefined被轉換為了window ``` 實際上,我們也可以將自執行函式,如```fn()```,看作是```fn.call()```的語法糖,在普通模式下,第一個引數預設為```undefined```,但被強制轉換為```window```。這也就解釋了為何所有自執行函式中```this```都指向```window```但無法通過```window```呼叫的問題(函式在```call```函式中掛載到```window```物件上,執行後被立即刪除,所以無法再次通過```window```訪問)。 ```apply()``` 或```call()```方法在嚴格模式下傳入簡單資料型別作為第一個引數時,該簡單資料型別會被轉換為相應的包裝類,而非嚴格模式不會如此轉換。 ```javascript function foo() { console.log(this); } foo.call(); // Window {} foo.call(2); // Number {2} function foo() { console.log(this); } foo.call(); // undefined foo.call(2); // 2 ``` ## 箭頭函式的```this```指向 在箭頭函式中, ```this```引用的是定義箭頭函式的上下文。即箭頭函式中的```this```**不會**隨著函式呼叫方式的改變而改變。 ```javascript function Person(name) { this.name = name; this.getName = () => console.log(this.name); } const p = new Person('Wango'); p.getName(); // Wango const getName = p.getName; getName(); // Wango getName.call({name: 'Lily'}); // Wango ``` 參考資料:
[Javascript 的 this 用法](http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html)
Javascript高階程式設計(