JavaScript 中最大的一個安全問題,也是最使人困惑的一個問題,就是在某些狀況下this
的值是如何肯定的。有js基礎的同窗面對這個問題基本能夠想到:this
的指向和函數調用的方式相關。這固然是正確的,然而,這幾種方式有什麼聯繫嗎?這是我接下來要說明的問題。javascript
this
從哪裏來this
是js的一個關鍵字,和arguments
相似,它是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。這句話彷佛與認知不一樣,咱們在函數體外部即全局做用域下也能使用this
。html
// 直接在全局做用域下輸出this console.log(this); // 輸出window
可是不要忘記,即使是全局做用域,依舊是運行在window
下的,咱們寫的代碼都在window
的某個函數中。而這也催生了一種理解this
指向的方法:this
永遠指向調用者(非箭頭函數中)。java
函數做爲普通函數直接調用(也稱爲自執行函數)的時候,不管函數在全局仍是在另外一個函數中,this
都是指向window
。數組
function fn() { this.author = 'Wango'; } fn(); console.log(author); // Wango
這很好理解,但又不是很好理解,由於在代碼中省略了window
,補全後就好理解了:this
指向的是調用者。安全
function fn() { this.author = 'Wango'; } window.fn(); console.log(window.author); // Wango
而在內部函數中,自執行函數中的this
依舊指向全局做用域,咱們沒法經過window.foo()
調用函數,但並不妨礙咱們先這樣理解(具體參見本文最後一部分this
的強制轉型)。app
function fn() { function foo() { console.log(this); } foo(); // Window window.foo(); // TypeError } fn();
在構造函數中,this
指向new
生成的新對象,即構造函數是經過new
調用的,構造函數內部的this
固然就應該指向new
出來的對象。函數
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
出來的對象。固然,這是構造函數的特性,與本主題關係不大。this
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
行爲相同,指向該方法的調用者。spa
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
指向經常會被咱們混淆、忽略。prototype
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
指向這個數組或僞數組,與對象調用函數的行爲一致。
function fn() { console.log(this.name); } const author = { name: 'Wango' } fn.call(author); // Wango
這彷佛與this
永遠指向調用者相違背,但一旦咱們明白了call函數的實現機制就會明白,這不只不是違背,反而是佐證。對call
、apply
、bind
實現機制不熟悉的同窗能夠參考我另外一篇文章,下面截取call
簡要說明。
// 保存一個全局變量做爲默認值 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
的根本緣由。
// 定義一個全局變量 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()
方法在嚴格模式下傳入簡單數據類型做爲第一個參數時,該簡單數據類型會被轉換爲相應的包裝類,而非嚴格模式不會如此轉換。
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
不會隨着函數調用方式的改變而改變。
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 用法 Javascript高級程序設計(第四版)