JavaScript作為一種腳本語(yǔ)言身份的存在,因此被很多人認(rèn)為是簡(jiǎn)單易學(xué)的。然而情況恰恰相反,JavaScript支持函數(shù)式編程、閉包、基于原型的繼承等高級(jí)功能。由于其運(yùn)行期綁定的特性,JavaScript 中的 this 含義要豐富得多,它可以是全局對(duì)象、當(dāng)前對(duì)象或者任意對(duì)象,這完全取決于函數(shù)的調(diào)用方式。JavaScript中函數(shù)的調(diào)用有以下幾種方式:作為對(duì)象方法調(diào)用,作為函數(shù)調(diào)用,作為構(gòu)造函數(shù)調(diào)用,和使用 apply 或 call 調(diào)用。本文就采擷些例子以淺顯說(shuō)明在不同調(diào)用方式下的不同含義。
『有則推薦』: 自 2017 年初,就有開(kāi)始利用閑余時(shí)光,打磨個(gè)人最新作品——「傾城之鏈」 ,有意將其打造成優(yōu)良開(kāi)放型平臺(tái),旨在云集全球優(yōu)秀網(wǎng)站,讓您更為便捷地探索互聯(lián)網(wǎng)中那更廣闊的世界;在這里,您可以輕松發(fā)現(xiàn)、學(xué)習(xí)、分享更多
有用
或有趣
的事物。目前仍在不斷迭代、優(yōu)化中,如果您對(duì)此感興趣,不妨先嘗試一下: 「傾城之鏈」;亦十分歡迎提出您寶貴意見(jiàn)或建議。 (Upade@2018-01-23 于深圳.南山)。也可以通過(guò)微信,掃描如下「小程序碼」訪問(wèn)體驗(yàn)。
全局的this
全局this一般指向全局對(duì)象,瀏覽器中的全局對(duì)象就是 window。例如:
console.log(this.document === document); //true
console.log(this === window); //true
this.a = 91;
console.log(window.a); //91
一般函數(shù)的 this
function f1 () {
return this;
}
console.log(f1() === window);//true, global object
可以看到一般函數(shù)的 this 也指向 window,在 nodeJS 中為 global object
function f2 () {
"use strict";//使用嚴(yán)格模式
return this;
}
console.log(f1() === undefined);//true
嚴(yán)格模式中,函數(shù)的 this 為 undefined,因?yàn)閲?yán)格模式禁止this關(guān)鍵字指向全局對(duì)象;對(duì)于js“嚴(yán)格模式”具體可以看阮一峰先生的Javascript 嚴(yán)格模式詳解
作為對(duì)象方法的函數(shù)的 this
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
上述代碼通過(guò)字面量創(chuàng)建對(duì)象 o。
f 為對(duì)象 o 的方法。這個(gè)方法的 this 指向這個(gè)對(duì)象,在這里即對(duì)象 o。
var o = {
prop: 37
};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
上面的代碼,創(chuàng)建了對(duì)象 o,但是沒(méi)有給對(duì)象 o,添加方法。而是通過(guò) o.f = independent 臨時(shí)添加了方法屬性。這樣這個(gè)方法中的 this 同樣也指向這個(gè)對(duì)象 o。
作為函數(shù)調(diào)用
函數(shù)也可以直接被調(diào)用,此時(shí) this 綁定到全局對(duì)象。在瀏覽器中,window 就是該全局對(duì)象。比如下面的例子:函數(shù)被調(diào)用時(shí),this被綁定到全局對(duì)象,接下來(lái)執(zhí)行賦值語(yǔ)句,相當(dāng)于隱式的聲明了一個(gè)全局變量,這顯然不是調(diào)用者希望的。
function makeNoSense(x) {
this.x = x;
}
makeNoSense(5);
x;// x 已經(jīng)成為一個(gè)值為 5 的全局變量
對(duì)于內(nèi)部函數(shù),即聲明在另外一個(gè)函數(shù)體內(nèi)的函數(shù),這種綁定到全局對(duì)象的方式會(huì)產(chǎn)生另外一個(gè)問(wèn)題。以下面moveTo方法為例,內(nèi)定義兩個(gè)函數(shù),分別將 x,y 坐標(biāo)進(jìn)行平移。結(jié)果可能出乎大家意料,不僅 point 對(duì)象沒(méi)有移動(dòng),反而多出兩個(gè)全局變量 x,y。
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
// 內(nèi)部函數(shù)
var moveX = function(x) {
this.x = x;//this 綁定到了哪里?
};
// 內(nèi)部函數(shù)
var moveY = function(y) {
this.y = y;//this 綁定到了哪里?
};
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x) //0
console.log(point.x) //0
console.log(x) //1
console.log(y) //1
這屬于 JavaScript 的設(shè)計(jì)缺陷,正確的設(shè)計(jì)方式是內(nèi)部函數(shù)的this應(yīng)該綁定到其外層函數(shù)對(duì)應(yīng)的對(duì)象上,為了規(guī)避這一設(shè)計(jì)缺陷,聰明的JavaScript程序員想出了變量替代的方法,約定俗成,該變量一般被命名為 that。
對(duì)象原型鏈上的this
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 2;
console.log(p.f()); //3
通過(guò) var p = Object.create(o) 創(chuàng)建的對(duì)象,p 是基于原型 o 創(chuàng)建出的對(duì)象。
p 的原型是 o,調(diào)用 f() 的時(shí)候是調(diào)用了 o 上的方法 f(),這里面的 this 是可以指向當(dāng)前對(duì)象的,即對(duì)象 p。
get/set 方法與 this
function modulus() {
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase() {
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(o, 'modulus', {
get: modulus,
enumerable: true,
configurable: true
});
console.log(o.phase, o.modulus); // -0.78 1.4142
get/set 方法中的 this 也會(huì)指向 get/set 方法所在的對(duì)象的。
構(gòu)造器中的 this
function MyClass() {
this.a = 25;
}
var o = new MyClass();
console.log(o.a); //25
new MyClass() 的時(shí)候,MyClass()中的 this 會(huì)指向一個(gè)空對(duì)象,這個(gè)對(duì)象的原型會(huì)指向 MyClass.prototype。MyClass()沒(méi)有返回值或者返回為基本類(lèi)型時(shí),默認(rèn)將 this 返回。
function C2() {
this.a = 26;
return {
a: 24
};
}
o = new C2();
console.log(o.a); //24
因?yàn)榉祷亓藢?duì)象,將這個(gè)對(duì)象作為返回值
call/apply 方法與 this
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {
a: 1,
b: 3
};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // "[object Number]"
bar.call(); //[object global]
bar.call("7");//[object String]
bar.call(true);//[object Boolean]
console.log(add.call(o,5,7));//16
bind 方法與 this
function f() {
return this.a;
}
var g = f.bind({
a: "test"
});
console.log(g()); // test
var o = {
a: 37,
f: f,
g: g
};
console.log(o.f(), o.g()); // 37, test
綁定之后再調(diào)用時(shí),仍然會(huì)按綁定時(shí)的內(nèi)容走,所以 o.g() 結(jié)果是 test
JavaScript中this的些許看似怪異現(xiàn)象
<body>
<!--JavaScript偽協(xié)議和內(nèi)聯(lián)事件對(duì)于this的指向不同-->
<a href="#" onclick="alert(this.tagName);">click me</a> <!--彈出A-->
<a href="javascript:alert(this.tagName);">click me</a> <!--彈出undefined-->
<a href="javascript:alert(this==window);">click me</a> <!--彈出true-->
<input id="btn" type="button" value="this demo" name="button"/>
</body>
var name = 'somebody';
var angela = {
name: 'angela',
say: function () {
alert("I'm " + this.name);
}
};
var btn = document.getElementById('btn');
setTimeout和setInterval也會(huì)改變this的指向
angela.say();//I'm angela
setTimeout(angela.say, 1000); //I'm somebody
setInterval(angela.say, 1000); //I'm somebody
on...也會(huì)改變this的指向
angela.say(); //I'm angela
btn.onclick = angela.say; //I'm button
click等回調(diào)也會(huì)改變this指向
$("#btn").click = angela.say; // I'm button
$("#btn").click(angela.say); // I'm button
如果在say中用了this,this會(huì)綁定在angela上么?顯然這里不是,賦值以后,函數(shù)是在回調(diào)中執(zhí)行的,this會(huì)綁定到$(“#btn”)元素上。這個(gè)函數(shù)被完整復(fù)制到onclick屬性(現(xiàn)在成為了函數(shù))。因此如果這個(gè)even thandler被執(zhí)行,this將指向HTML元素;因此,結(jié)果顯示的是"I'm button"。而,匿名函數(shù)可以調(diào)整this指向,EG:
$("#btn").click(function(){
angela.say(); //I'm angela
});
這是JavaScript新手們經(jīng)常犯的一個(gè)錯(cuò)誤,為了避免這種錯(cuò)誤,許多JavaScript框架都提供了手動(dòng)綁定 this 的方法。比如Dojo就提供了lang.hitch,該方法接受一個(gè)對(duì)象和函數(shù)作為參數(shù),返回一個(gè)新函數(shù),執(zhí)行時(shí)this綁定到傳入的對(duì)象上。使用 Dojo,可以將上面的例子改為:
button.onclick = lang.hitch(angela, angela.say);
其實(shí)在我們使用比較多的jQuery也提供了對(duì)應(yīng)的解決方案:jQuery.proxy(function, scope).返回一個(gè)新函數(shù),并且這個(gè)函數(shù)始終保持了特定的作用域。其作用跟Dojo就提供了lang.hitch類(lèi)似,具體可以參考這里。其中有一例如下:
<div id="test">Click Here!</div> //html Code
var obj = {
name: "John",
test: function() {
alert( this.name );
$("#test").unbind("click", obj.test);
}
};
$("#test").click( jQuery.proxy( obj, "test" ) );
//強(qiáng)制設(shè)置函數(shù)的作用域,讓this指向obj而不是#test對(duì)象。
// 以下代碼跟上面那句是等價(jià)的:
// $("#test").click( jQuery.proxy( obj.test, obj ) );
// 可以與單獨(dú)執(zhí)行下面這句做個(gè)比較。
// $("#test").click( obj.test );
在新版的 JavaScript 中,已經(jīng)提供了內(nèi)置的 bind 方法供大家使用。
匿名函數(shù)調(diào)整this指向比如:
setTimeout(function () { angela.say(); }, 1000); //I'm angela
setInterval(function () { angela.say(); }, 1000) //I'm angela
btn.onclick = function () { angela.say(); }; //I'm angela
setTimeout(function () { alert(this == window); }, 1000);//true
btn.onclick = function () { alert(this == btn); }//true
匿名函數(shù)賦值給了click屬性(好吧,現(xiàn)在成了函數(shù)),此時(shí)這個(gè)匿名函數(shù)指向的即是Html屬性。因此所調(diào)用的函數(shù)(比如angela.say())this上下文沒(méi)有被更改,所以其打印出來(lái)的結(jié)果就是'I'm angela'。事實(shí)上,也用這樣的方法來(lái)消解this在回調(diào)函數(shù)中不堪使用的'特色'。
$("#btn").click(function(){
if(window == this){
alert("window == this");
}else{
alert("window != this") //彈出來(lái)
}
alert(this.name); // button
angela.say(); //I'm angela
});
將this指向的對(duì)象保存到變量(一般用that)
var mydemo = {
name: 'angela',
say: function () { alert("I'm " + this.name); },
init: function () {
var that = this;
document.getElementById('btn').onclick = function () {
that.say(); //彈出Alert:I'm angela
this.say(); //這兒報(bào)錯(cuò): undefined is not a function (evaluating 'this.say()')
}
}
};
mydemo.init();
第三方庫(kù)or框架中的this
比如,使用backbone框架中events時(shí)間回調(diào)中的this,其指向的就是對(duì)應(yīng)的視圖,而不是Dom元素,因?yàn)樵摶卣{(diào)時(shí)通過(guò)events哈希綁定的,實(shí)質(zhì)上也是自對(duì)應(yīng)視圖那里callback到對(duì)應(yīng)的函數(shù);
Javascript中的eval 方法
JavaScript 中的 eval 方法可以將字符串轉(zhuǎn)換為 JavaScript 代碼,使用 eval 方法時(shí),this 指向哪里呢?答案很簡(jiǎn)單,看誰(shuí)在調(diào)用 eval 方法,調(diào)用者的執(zhí)行環(huán)境(ExecutionContext)中的 this 就被 eval 方法繼承下來(lái)了。(悪,還沒(méi)用過(guò),有待實(shí)踐下)!
但是:在嚴(yán)格模式之下,eval的作用域也被改變了。正常模式下,eval語(yǔ)句的作用域,取決于它處于全局作用域,還是處于函數(shù)作用域。嚴(yán)格模式下,eval語(yǔ)句本身就是一個(gè)作用域,不再能夠生成全局變量了,它所生成的變量只能用于eval內(nèi)部。
"use strict";
var x = 2;
console.info(eval("var x = 5; x")); // 5
console.info(x); // 2
后記:由于javascript的動(dòng)態(tài)性(解釋執(zhí)行,當(dāng)然也有簡(jiǎn)單的預(yù)編譯過(guò)程),this的指向在運(yùn)行時(shí)才確定,因此在只要足夠留心其運(yùn)行時(shí)的上下文,即可無(wú)痛揮霍this的強(qiáng)大。
原文鏈接:http://www.jeffjade.com/2015/08/03/2015-08-03-javascript-this/
參考AJavaScript 中的 this
參考BJavaScript中this的一些怪異現(xiàn)象
參考CJavascript的this用法-阮一峰
參考D深入淺出 JavaScript 中的 this