不得不承認JS中存在一些“歷史的遺產”,比如基於全局變量的編程模型。
如果寫一個函數,如:
function test(name){
var namex = name
console.log(this.namex)
}
test('jack')
方法調用之後,this.namex=undefined
,爲何呢?因爲此處的this是全局對象window
,這就令人比較頭大。
不過彆着急,慢慢往下看,關於this
還有很多魔幻的現實,我們來一一解鎖。
1、完整的函數調用
還是上面的代碼,其實他的完整調用寫法是這樣的:
function test(name){
var namex = name
console.log(this.namex)
}
test.call(undefined, 'jack')
test
是一個函數對象--即Function
對象,Function.prototype
有call
方法。call()
會有兩個參數,第一個參數是this上下文對象,第二個參數是函數入參列表。
如果call()
傳入的this
上下文是undefined
或null
,那麼window
對象將成爲默認的this
上下文。這也就解釋了開頭例子中this爲啥爲window的原因了
2、對象中的this
const obj = {
name: 'Jack',
greet: function() {
console.log(this.name)
}
}
obj.greet() //簡寫調用
obj.greet.call(obj) //完整調用
obj.greet()
中的this
無疑就是obj
對象
3、對象方法中嵌套函數的this
對2中的代碼進行修改:
const obj = {
name: 'Jack',
greet: function() {
retufn function(){console.log(this.name)}
}
}
obj.greet()() //輸出undefined
需要注意的是嵌套函數中的this
依然是window
,爲什麼呢?可以拆分來看:
var greet = obj.greet()
greet() // = greet.call(undefined)
嵌套函數被調用的時候,真實的調用者上下文是undefined
,也就是window
4、原型與this
function Clt() {
}
Clt.prototype.x = 10
Clt.prototype.test = function () {
console.log(this)
this.y = this.x + 1
}
let bean = new Clt()
bean.test();
console.log(bean.y)
test方法中輸出的this是一個Clt對象。
這裏需要引入一個新的概念:構造器函數。
new關鍵字就是構造器函數,它的作用是:
一旦函數被new來調用,就會創建一個鏈接到該函數的prototype屬性的新對象,同時this會被綁定到那個新對象上
理解了構造器函數,我們就理解了原型與this的關係了,因爲new會重新指定this上下文
5、箭頭函數與this
ECMAScript6出現了箭頭函數的用法,關於箭頭函數中的this,需要先記住一句話:
函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
看代碼(引自阮一峯老師的教程:https://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0):
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
此處的this指向了foo的this上下文,即定義時的this對象。
箭頭函數與非箭頭函數的this區別:
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭頭函數
setInterval(() => this.s1++, 1000);
// 普通函數
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
箭頭函數的this指向了定義時所在對象的this;非箭頭函數的this指向了運行時的作用域,即全局域window
this的指向固定化,也算是解決了一些歷史舊賬,無疑帶來的很大好處,比如封裝調用:
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
一個嵌套的例子:
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
最裏層的this也綁定到了定義時所在對象的—即foo的this
總結
ECMAScript6的箭頭函數也算是解決了一些歷史問題。不過正如HTML5離不開HTML4一樣,因爲只有保持了繼承與兼容,新技術的推廣纔會更加的迅速。
享受語言新特性之餘,也要搞清楚舊版本的一些細節,這有助於更全面的掌握知識。
希望這篇文章對您有用!