關於this的那些事
- 什麼是this
- 詞法作用域與動態作用域
- this的繫結
一、什麼是this
this表示關鍵字,也是一個常見的代詞,經常來表示一個上下文物件,那上下文物件又是什麼呢?
var me = { name: 'JJ Yu' }; var you = { name: 'Kim' }; function sayName () { console.log(this.name); } // 1 sayName.call(me); // output: JJ Yu // 2 sayName.call(you); // output: Kim var name = 'global name'; // 3 sayName(); // output: global name複製程式碼
上面程式碼我們主要關注一點就是執行上下文,在1、2的位置分別繫結me、you上下文物件,所以輸出的是相對應的name值;在3位置前面定義了一個全域性的name變數,並且在沒有指定上下文環境被輸出了,這也涉及到this的隱式繫結(在"this的繫結"講會詳細說明),繫結到了當前環境window。
下面我們把`sayName`函式顯式的傳遞上下文物件:
// 宣告一個傳遞上下文引數的函式 function sayName (context) { console.log(context.name); } sayName(me); // output: JJ Yu sayName(you); // output: Kim 複製程式碼
相比第一種(隱式繫結),顯示傳遞上下文顯得複雜,而且程式碼模式越複雜,這樣傳遞上下文物件就會讓程式碼越來越亂。
二、動態作用域和詞法作用域
例1:
function increase (i) { // this不是指向函式本身,而是指向了呼叫函式的上下文環境`window` this.count++; console.log(i); } increase.count = 0; for(var i = 0; i < 5; i++) { increase(i); } console.log(increase.count); // output: 0 // output: // 0 // 1 // 2 // 3 // 4 // 0 複製程式碼
例2:
function increase (i) { // 修改了,把指向指定到`increase` increase.count++; console.log(i); } increase.count = 0; for(var i = 0; i < 5; i++) { increase(i); } console.log(increase.count); // output: 5 // output: // 0 // 1 // 2 // 3 // 4 // 5複製程式碼
例1中的this.count中的this並不是指向函式物件,而是在被呼叫時候指向了`window`;而例2使用`詞法作用域`直接指定increase物件的count屬性。
那為啥例1的this會被動態繫結到window物件呢?
// window function increase (i) { // this不是指向函式本身,而是指向了呼叫函式的上下文環境`window` this.count++; console.log(i); } increase(0); // 等價於 window.increase(0) // 即是呼叫window下的方法,this指向window 複製程式碼
如果還有點不明白,來看下下面常見的場景:
var env = 'global'; function foo () { this.env = 'foo function'; setTimeout(function () { console.log(this.env); // ? console.log(this === window); // true }) } foo(); 複製程式碼
那麼問號那行會輸出:foo function;
總結:
- 函式中的this不是指向函式本身也不指向函式的詞法作用域(見例1)
- this的繫結是發生在函式被呼叫時,至於this指向誰取決於函式在哪裡被呼叫
三、this的繫結
在前面我們已經總結了this的指向,也就是繫結,是發生在函式被呼叫時。
呼叫位置:即函式在程式碼中被呼叫的位置(不是宣告的位置),在這裡只提及一下。
1、預設繫結(見例1)
這裡需要注意的嚴格模式(strict mode)不能將全域性物件用於預設繫結,此時this會繫結到undefined。
function foo () { "strict mode"; console.log(this.env); } var env = 'global'; foo(); // TypeError: this is undefined複製程式碼
但在嚴格模式下呼叫函式不影響預設繫結:
function foo () { "strict mode"; console.log(this.env); } var env = 'global'; (function () { "strict mode"; foo(); // output: global })();複製程式碼
2、隱式繫結
function foo () { console.log(this.env); } var obj = { env: 'obj', foo: foo } obj.foo(); // output: obj複製程式碼
當foo()被呼叫時,它的前面加上了obj物件引用,呼叫位置會使用obj上下文來引用函式。
function foo () { console.log(this.env); } var obj = { env: 'obj', foo: foo // => window -> obj } var f = obj.foo; // => obj -> window var env = 'global'; f(); // output: global 複製程式碼
3、顯示繫結
Function物件包含call以及apply兩個方法,用來指定this的指向。詳細用法自行百度。
var obj = { name: 'JJ Yu' } var name = 'window'; function sayName () { console.log(this.name); } sayName(); // output: window sayName.call(obj); // output: JJ Yu 複製程式碼
硬繫結
var obj = { name: 'JJ Yu' } function sayName () { console.log(this.name); } function forceBind () { sayName.call(obj); } setTimeout(forceBind);複製程式碼
es5提供了內建方法Function.prototype.bind:
var obj = { name: 'JJ Yu' } function sayName () { console.log(this.name); } setTimeout(sayName.bind(obj));複製程式碼
該方法返回一個硬編碼(不知道是啥東東)的新函式,指定this的上下文以及呼叫原函式。
new繫結
var Person = function (name) { this.name = name; this.sayName = function () { console.log(this.name); } } var jj = new Person('JJ'); jj.sayName(); // output: JJ複製程式碼