爲了更好地理解 this,將 this 使用的場景分紅三類:api
在函數內部 this 一個額外的,一般是隱含的參數。瀏覽器
在函數外部(頂級做用域中): 這指的是瀏覽器中的全局對象或者 Node.js 中一個模塊的輸出。安全
在傳遞給eval()的字符串中: eval() 或者獲取 this 當前值值,或者將其設置爲全局對象,取決於 this 是直接調用仍是間接調用。app
咱們來看看每一個類別。函數
這是最經常使用的 this 使用方式,函數經過扮演三種不一樣的角色來表示 JavaScript 中的全部可調用結構體:oop
普通函數(this 在非嚴格模式下爲全局對象,在嚴格模式下爲undefined)this
構造函數(this 指向新建立的實例)spa
方法(this 是指方法調用的接收者)prototype
在函數中,this 一般被認爲是一個額外的,隱含的參數。對象
在普通函數中,this 的值取決於模式:
非嚴格模式: this 是指向全局對象 (在瀏覽器中爲window對象)。
function sloppyFunc() {
console.log(this === window); // true
}
sloppyFunc();
嚴格模式: this 的值爲 undefined。
function strictFunc() {
'use strict';
console.log(this === undefined); // true
}
strictFunc();
也就是說,this 是一個設定了默認值(window或undefined)的隱式參數。 可是,能夠經過 call() 或 apply() 進行函數調用,並明確指定this的值:
function func(arg1, arg2) {
console.log(this); // a
console.log(arg1); // b
console.log(arg2); // c
}
func.call('a', 'b', 'c'); // (this, arg1, arg2)
func.apply('a', ['b', 'c']); // (this, arrayWithArgs)
若是經過new運算符調用函數,則函數將成爲構造函數。 該運算符建立一個新的對象,並經過this傳遞給構造函數:
var savedThis;
function Constr() {
savedThis = this;
}
var inst = new Constr();
console.log(savedThis === inst); // true
在JavaScript中實現,new運算符大體以下所示(更精確的實現稍微複雜一點):
function newOperator(Constr, arrayWithArgs) {
var thisValue = Object.create(Constr.prototype);
Constr.apply(thisValue, arrayWithArgs);
return thisValue;
}
在方法中,相似於傳統的面向對象的語言:this指向接受者,方法被調用的對象。
var obj = {
method: function () {
console.log(this === obj); // true
}
}
obj.method();
在瀏覽器中,頂層做用域是全局做用域,它指向 global object(如window):
console.log(this === window); // true
在Node.js中,一般在模塊中執行代碼。 所以,頂級做用域是一個特殊的模塊做用域:
// `global` (不是 `window`) 指全局對象:
console.log(Math === global.Math); // true
// `this` 不指向全局對象:
console.log(this !== global); // true
// `this` refers to a module’s exports:
console.log(this === module.exports); // true
eval() 能夠被直接(經過真正的函數調用)或間接(經過其餘方式)調用。
若是間接調用evaleval() ,則this指向全局對象:
(0,eval)('this === window')
true
不然,若是直接調用eval() ,則this與eval()的環境中保持一致。 例如:
// 普通函數
function sloppyFunc() {
console.log(eval('this') === window); // true
}
sloppyFunc();
function strictFunc() {
'use strict';
console.log(eval('this') === undefined); // true
}
strictFunc();
// 構造器
var savedThis;
function Constr() {
savedThis = eval('this');
}
var inst = new Constr();
console.log(savedThis === inst); // true
// 方法
var obj = {
method: function () {
console.log(eval('this') === obj); // true
}
}
obj.method();
有三個你須要知道的與this相關的陷阱。請注意,在各類狀況下,嚴格模式更安全,由於this在普通函數中爲undefined,而且會在出現問題時警告。
若是你調用一個構造函數時忘記了new操做符,那麼你意外地將this用在一個普通的函數。this會沒有正確的值。 在非嚴格模式下,this指向window對象,你將建立全局變量:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p = Point(7, 5); // 忘記new!
console.log(p === undefined); // true
// 建立了全局變量:
console.log(x); // 7
console.log(y); // 5
幸運的是,在嚴格模式下會獲得警告(this === undefined):
function Point(x, y) {
'use strict';
this.x = x;
this.y = y;
}
var p = Point(7, 5);
// TypeError: Cannot set property 'x' of undefined
若是獲取方法的值(不是調用它),則能夠將該方法轉換爲函數。 調用該值將致使函數調用,而不是方法調用。 當將方法做爲函數或方法調用的參數傳遞時,可能會發生這種提取。 實際例子包括setTimeout()和事件註冊處理程序。 我將使用函數callItt() 來模擬此用例:
/**相似setTimeout() 和 setImmediate() */
function callIt(func) {
func();
}
若是在非嚴格模式下把一個方法做爲函數來調用,那麼this將指向全局對象並建立全局變量:
var counter = {
count: 0,
// Sloppy-mode method
inc: function () {
this.count++;
}
}
callIt(counter.inc);
// Didn’t work:
console.log(counter.count); // 0
// Instead, a global variable has been created
// (NaN is result of applying ++ to undefined):
console.log(count); // NaN
若是在嚴格模式下把一個方法做爲函數來調用,this爲undefined。 同時會獲得一個警告:
var counter = {
count: 0,
// Strict-mode method
inc: function () {
'use strict';
this.count++;
}
}
callIt(counter.inc);
// TypeError: Cannot read property 'count' of undefined
console.log(counter.count);
修正方法是使用bind():
var counter = {
count: 0,
inc: function () {
this.count++;
}
}
callIt(counter.inc.bind(counter));
// 成功了!
console.log(counter.count); // 1
bind()建立了一個新的函數,它老是能獲得一個指向counter的this。
當在一個方法中使用普通函數時,很容易忘記前者具備其本身this(即便其不須要this)。 所以,你不能從前者引用該方法的this,由於該this會被遮蔽。 讓咱們看看出現問題的例子:
var obj = {
name: 'Jane',
friends: [ 'Tarzan', 'Cheeta' ],
loop: function () {
'use strict';
this.friends.forEach(
function (friend) {
console.log(this.name+' knows '+friend);
}
);
}
};
obj.loop();
// TypeError: Cannot read property 'name' of undefined
在前面的例子中,獲取this.name失敗,由於函數的this個是undefined,它與方法loop()的不一樣。 有三種方法能夠修正this。
修正1: that = this。 將它分配給一個沒有被遮蔽的變量(另外一個流行名稱是self)並使用該變量。
loop: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' knows '+friend);
});
}
修正2: bind()。 使用bind()來建立一個this老是指向正確值的函數(在下面的例子中該方法的this)。
loop: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}.bind(this));
}
修正3: forEach的第二個參數。 此方法具備第二個參數,this值將做爲此值傳遞給回調函數。
loop: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}, this);
}
從概念上講,我認爲普通函數沒有它本身的this,而且想到上述修復是爲了保持這種想法。 ECMAScript 6經過箭頭函數支持這種方法 - 沒有它們本身的this。 在這樣的函數裏面,你能夠自由使用this,由於不會被屏蔽:
loop: function () {
'use strict';
// The parameter of forEach() is an arrow function
this.friends.forEach(friend => {
// `this` is loop’s `this`
console.log(this.name+' knows '+friend);
});
}
我不喜歡使用this做爲普通函數的附加參數的API:
beforeEach(function () {
this.addMatchers({
toBeInRange: function (start, end) {
...
}
});
});
將這樣的隱含參數變成明確的參數使得事情更加明顯,而且與箭頭函數兼容。
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
...
}
});
});