談及 Javascript 中的 this,竟然讓人覺得頭疼,它不像 Java,C++ 中的 this 指向調(diào)用 this 的該對(duì)象。
在函數(shù)中 this 到底取何值,是在函數(shù)真正被調(diào)用執(zhí)行的時(shí)候確定下來的,函數(shù)定義的時(shí)候確定不了。
因?yàn)?this 的取值是執(zhí)行上下文環(huán)境的一部分,每次調(diào)用函數(shù),都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境。當(dāng)你在代碼中使用了 this,這個(gè) this 的值就直接從執(zhí)行的上下文中獲取了,而不會(huì)從作用域鏈中搜尋。
關(guān)于 this 的取值,大體上可以分為以下八種情況:
情況一:全局 & 調(diào)用普通函數(shù)
在全局環(huán)境中,this 永遠(yuǎn)指向 window。
console.log(this === window);? ? //true
普通函數(shù)在調(diào)用時(shí)候(注意不是構(gòu)造函數(shù),前面不加 new),其中的 this 也是指向 window。
但是如果在嚴(yán)格模式下調(diào)用的話會(huì)報(bào)錯(cuò):
var x = 10;
function foo(){
? ? console.log(this);? ? // undefined
? ? console.log(this.x);? // Uncaught TypeError: Cannot read property 'x' of undefined
}
foo();
情況二:構(gòu)造函數(shù)
所謂的構(gòu)造函數(shù)就是由一個(gè)函數(shù) new 出來的對(duì)象,一般構(gòu)造函數(shù)的函數(shù)名首字母大寫,例如像 Object,F(xiàn)unction,Array 這些都屬于構(gòu)造函數(shù)。
function Foo(){
? ? this.x = 10;
? ? console.log(this);? ? //Foo {x:10}
}
var foo = new Foo();
console.log(foo.x);? ? ? //10
上述代碼,如果函數(shù)作為構(gòu)造函數(shù)使用,那么其中的 this 就代表它即將 new 出來的對(duì)象。
但是如果直接調(diào)用 Foo 函數(shù),而不是 new Foo(),那就變成情況1,這時(shí)候 Foo() 就變成普通函數(shù)。
function Foo(){
? ? this.x = 10;
? ? console.log(this);? ? //Window
}
var foo = Foo();
console.log(foo.x);? ? ? //undefined
情況三:對(duì)象方法
如果函數(shù)作為對(duì)象的方法時(shí),方法中的 this 指向該對(duì)象。
var obj = {
? ? ? ? x: 10,
? ? foo: function () {
? ? ? ? console.log(this);? ? ? ? //Object
? ? ? ? console.log(this.x);? ? ? //10
? ? }
};
obj.foo();
注意:若是在對(duì)象方法中定義函數(shù),那么情況就不同了。
var obj = {
? ? x: 10,
? ? foo: function () {
? ? ? ? function f(){
? ? ? ? ? ? console.log(this);? ? ? //Window
? ? ? ? ? ? console.log(this.x);? ? //undefined
? ? ? ? }
? ? ? ? f();
? ? }
}
obj.foo();
可以這么理解:函數(shù) f 雖然是在 obj.foo 內(nèi)部定義的,但它仍然屬于一個(gè)普通函數(shù),this 仍指向 window。(這是個(gè)坑,要記牢)
在這里,如果想要調(diào)用上層作用域中的變量 obj.x,可以使用 self 緩存外部 this 變量。
var obj = {
? ? x: 10,
? ? foo: function () {
? ? ? ? var self = this;
? ? ? ? function f(){
? ? ? ? ? ? console.log(self);? ? ? //{x: 10}
? ? ? ? ? ? console.log(self.x);? ? //10
? ? ? ? }
? ? ? ? f();
? ? }
}
obj.foo();
如果 foo 函數(shù)不作為對(duì)象方法被調(diào)用:
var obj = {
? ? x: 10,
? ? foo: function () {
? ? ? ? console.log(this);? ? ? //Window
? ? ? ? console.log(this.x);? ? //undefined
? ? }
};
var fn = obj.foo;
fn();
obj.foo 被賦值給一個(gè)全局變量,并沒有作為 obj 的一個(gè)屬性被調(diào)用,那么此時(shí) this 的值是 window。
情況四:構(gòu)造函數(shù) prototype 屬性
function Foo(){
? ? this.x = 10;
}
Foo.prototype.getX = function () {
? ? console.log(this);? ? ? ? //Foo {x: 10, getX: function}
? ? console.log(this.x);? ? ? //10
}
var foo = new Foo();
foo.getX();
在 Foo.prototype.getX 函數(shù)中,this 指向的 foo 對(duì)象。不僅僅如此,即便是在整個(gè)原型鏈中,this 代表的也是當(dāng)前對(duì)象的值。
情況五:函數(shù)用 call、apply或者 bind 調(diào)用。
var obj = {
? ? x: 10
}
function foo(){
? ? console.log(this);? ? //{x: 10}
? ? console.log(this.x);? //10
}
foo.call(obj);
foo.apply(obj);
foo.bind(obj)();
當(dāng)一個(gè)函數(shù)被 call、apply 或者 bind 調(diào)用時(shí),this 的值就取傳入的對(duì)象的值。
情況六:DOM event this
在一個(gè) HTML DOM 事件處理程序里,this 始終指向這個(gè)處理程序所綁定的 HTML DOM 節(jié)點(diǎn):
function Listener(){?
? ? document.getElementById('foo').addEventListener('click', this.handleClick);? ? //這里的 this 指向 Listener 這個(gè)對(duì)象。不是強(qiáng)調(diào)的是這里的 this
}
Listener.prototype.handleClick = function (event) {
? ? console.log(this);? ? //<div id="foo"></div>
}
var listener = new Listener();
document.getElementById('foo').click();
這個(gè)很好理解,就相當(dāng)于是給函數(shù)傳參,使 handleClick 運(yùn)行時(shí)上下文改變了,相當(dāng)于下面這樣的代碼:
var obj = {
? ? x: 10,
? ? fn: function() {
? ? ? ? console.log(this);? ? ? ? //Window
? ? ? ? console.log(this.x);? ? ? //undefined
? ? }
};
function foo(fn) {
? ? fn();
}
foo(obj.fn);
你也可以用通過 bind 切換上下文:
function? Listener(){
? ? document.getElementById('foo').addEventListener('click',this.handleClick.bind(this));? ? ?
}
Listener.prototype.handleClick = function (event) {
? ? console.log(this);? ? //Listener {}
}
var listener = new Listener();
document.getElementById('foo').click();
前六種情況總結(jié)一句話為: this 指向調(diào)用該方法的對(duì)象。
情況七:箭頭函數(shù)中的 this
當(dāng)使用箭頭函數(shù)的時(shí)候,情況就有所不同了:箭頭函數(shù)內(nèi)部的 this 是詞法作用域,由上下文確定。
var obj = {
? ? x: 10,
? ? foo: function() {
? ? ? ? var fn = () => {
? ? ? ? ? ? return () => {
? ? ? ? ? ? ? ? return () => {
? ? ? ? ? ? ? ? ? ? console.log(this);? ? ? //Object {x: 10}
? ? ? ? ? ? ? ? ? ? console.log(this.x);? ? //10
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? fn()()();
? ? }
}
obj.foo();
現(xiàn)在,箭頭函數(shù)完全修復(fù)了 this 的指向,this 總是指向詞法作用域,也就是外層調(diào)用者 obj。
如果使用箭頭函數(shù),以前的這種 hack 寫法:
var self = this;
就不再需要了。
var obj = {
? ? x: 10,
? ? foo: function() {
? ? ? ? var fn = () => {
? ? ? ? ? ? return () => {
? ? ? ? ? ? ? ? return () => {
? ? ? ? ? ? ? ? ? ? console.log(this);? ? // Object {x: 10}
? ? ? ? ? ? ? ? ? ? console.log(this.x);? //10
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? fn.bind({x: 14})()()();
? ? ? ? fn.call({x: 14})()();
? ? }
}
obj.foo();
由于 this 在箭頭函數(shù)中已經(jīng)按照詞法作用域綁定了,所以,用 call()或者 apply()調(diào)用箭頭函數(shù)時(shí),無(wú)法對(duì) this 進(jìn)行綁定,即傳入的第一個(gè)參數(shù)被忽略。
補(bǔ)充說明:
this 為保留字,你不能重寫 this。
function test(){
? ? var this = {};? ? //Uncaught SyntaxError: Unexpected token this
}
宿主對(duì)象:
一門語(yǔ)言在運(yùn)行的時(shí)候,需要一個(gè)環(huán)境,叫做宿主環(huán)境。
對(duì)于JavaScript,宿主環(huán)境最常見的是 web 瀏覽器,瀏覽器提供了一個(gè) JavaScript 運(yùn)行的環(huán)境,這個(gè)環(huán)境里面,需要提供一些接口,好讓 JavaScript 引擎能夠和宿主環(huán)境對(duì)接。
JavaScript 引擎才是真正執(zhí)行 JavaScript 代碼的地方,常見的引擎有 V8(目前最快 JavaScript 引擎、Google 生產(chǎn))、JavaScript core。
在瀏覽器或者服務(wù)端( nodejs )都有自己的 JS 引擎,在瀏覽器中,全局對(duì)象為 window,而在 nodejs 中,全局對(duì)象為 global。