JavaScript 之 this 探究

JavaScript作為一種腳本語言身份的存在华坦,因此被很多人認(rèn)為是簡單易學(xué)的。然而情況恰恰相反不从,JavaScript支持函數(shù)式編程惜姐、閉包、基于原型的繼承等高級功能椿息。由于其運(yùn)行期綁定的特性歹袁,JavaScript 中的 this 含義要豐富得多,它可以是全局對象寝优、當(dāng)前對象或者任意對象宇攻,這完全取決于函數(shù)的調(diào)用方式。JavaScript中函數(shù)的調(diào)用有以下幾種方式:作為對象方法調(diào)用倡勇,作為函數(shù)調(diào)用,作為構(gòu)造函數(shù)調(diào)用,和使用 apply 或 call 調(diào)用妻熊。本文就采擷些例子以淺顯說明在不同調(diào)用方式下的不同含義夸浅。

『有則推薦』: 自 2017 年初,就有開始利用閑余時(shí)光扔役,打磨個(gè)人最新作品——「傾城之鏈」 帆喇,有意將其打造成優(yōu)良開放型平臺,旨在云集全球優(yōu)秀網(wǎng)站亿胸,讓您更為便捷地探索互聯(lián)網(wǎng)中那更廣闊的世界坯钦;在這里,您可以輕松發(fā)現(xiàn)侈玄、學(xué)習(xí)婉刀、分享更多有用有趣的事物。目前仍在不斷迭代序仙、優(yōu)化中突颊,如果您對此感興趣,不妨先嘗試一下: 「傾城之鏈」潘悼;亦十分歡迎提出您寶貴意見或建議律秃。 (Upade@2018-01-23 于深圳.南山)。也可以通過微信治唤,掃描如下「小程序碼」訪問體驗(yàn)棒动。

傾城之鏈 - 小程序

全局的this

全局this一般指向全局對象,瀏覽器中的全局對象就是 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)鍵字指向全局對象辞槐;對于js“嚴(yán)格模式”具體可以看阮一峰先生的Javascript 嚴(yán)格模式詳解

作為對象方法的函數(shù)的 this

var o = {
    prop: 37,
    f: function() {
        return this.prop;
    }
};
console.log(o.f()); // 37

上述代碼通過字面量創(chuàng)建對象 o掷漱。

f 為對象 o 的方法。這個(gè)方法的 this 指向這個(gè)對象榄檬,在這里即對象 o卜范。

var o = {
    prop: 37
};

function independent() {
    return this.prop;
}
o.f = independent;
console.log(o.f()); // 37

上面的代碼,創(chuàng)建了對象 o鹿榜,但是沒有給對象 o海雪,添加方法。而是通過 o.f = independent 臨時(shí)添加了方法屬性舱殿。這樣這個(gè)方法中的 this 同樣也指向這個(gè)對象 o奥裸。

作為函數(shù)調(diào)用

函數(shù)也可以直接被調(diào)用,此時(shí) this 綁定到全局對象沪袭。在瀏覽器中湾宙,window 就是該全局對象。比如下面的例子:函數(shù)被調(diào)用時(shí),this被綁定到全局對象侠鳄,接下來執(zhí)行賦值語句埠啃,相當(dāng)于隱式的聲明了一個(gè)全局變量,這顯然不是調(diào)用者希望的伟恶。

function makeNoSense(x) { 
    this.x = x; 
} 
makeNoSense(5); 
x;// x 已經(jīng)成為一個(gè)值為 5 的全局變量

對于內(nèi)部函數(shù)碴开,即聲明在另外一個(gè)函數(shù)體內(nèi)的函數(shù),這種綁定到全局對象的方式會產(chǎn)生另外一個(gè)問題博秫。以下面moveTo方法為例潦牛,內(nèi)定義兩個(gè)函數(shù),分別將 x挡育,y 坐標(biāo)進(jìn)行平移巴碗。結(jié)果可能出乎大家意料,不僅 point 對象沒有移動静盅,反而多出兩個(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ù)對應(yīng)的對象上市咽,為了規(guī)避這一設(shè)計(jì)缺陷痊银,聰明的JavaScript程序員想出了變量替代的方法,約定俗成施绎,該變量一般被命名為 that溯革。

對象原型鏈上的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

通過 var p = Object.create(o) 創(chuàng)建的對象,p 是基于原型 o 創(chuàng)建出的對象谷醉。

p 的原型是 o致稀,調(diào)用 f() 的時(shí)候是調(diào)用了 o 上的方法 f(),這里面的 this 是可以指向當(dāng)前對象的俱尼,即對象 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 也會指向 get/set 方法所在的對象的。

構(gòu)造器中的 this

function MyClass() {
    this.a = 25;
}
var o = new MyClass();
console.log(o.a); //25

new MyClass() 的時(shí)候遇八,MyClass()中的 this 會指向一個(gè)空對象矛绘,這個(gè)對象的原型會指向 MyClass.prototype。MyClass()沒有返回值或者返回為基本類型時(shí)刃永,默認(rèn)將 this 返回货矮。

function C2() {
    this.a = 26;
    return {
        a: 24
    };
}

o = new C2();
console.log(o.a); //24

因?yàn)榉祷亓藢ο螅瑢⑦@個(gè)對象作為返回值

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í)斯够,仍然會按綁定時(shí)的內(nèi)容走囚玫,所以 o.g() 結(jié)果是 test


JavaScript中this的些許看似怪異現(xiàn)象

<body>
    <!--JavaScript偽協(xié)議和內(nèi)聯(lián)事件對于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也會改變this的指向

angela.say();//I'm  angela
setTimeout(angela.say, 1000);  //I'm  somebody
setInterval(angela.say, 1000); //I'm  somebody

on...也會改變this的指向

angela.say(); //I'm  angela
btn.onclick = angela.say; //I'm  button

click等回調(diào)也會改變this指向

$("#btn").click = angela.say;  // I'm  button
$("#btn").click(angela.say);   // I'm  button

如果在say中用了this喧锦,this會綁定在angela上么?顯然這里不是劫灶,賦值以后裸违,函數(shù)是在回調(diào)中執(zhí)行的,this會綁定到$(“#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è)錯誤宿稀,為了避免這種錯誤,許多JavaScript框架都提供了手動綁定 this 的方法赖捌。比如Dojo就提供了lang.hitch祝沸,該方法接受一個(gè)對象和函數(shù)作為參數(shù),返回一個(gè)新函數(shù)越庇,執(zhí)行時(shí)this綁定到傳入的對象上罩锐。使用 Dojo,可以將上面的例子改為:

button.onclick = lang.hitch(angela, angela.say);

其實(shí)在我們使用比較多的jQuery也提供了對應(yīng)的解決方案:jQuery.proxy(function, scope).返回一個(gè)新函數(shù)卤唉,并且這個(gè)函數(shù)始終保持了特定的作用域涩惑。其作用跟Dojo就提供了lang.hitch類似,具體可以參考這里桑驱。其中有一例如下:

<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對象。

// 以下代碼跟上面那句是等價(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上下文沒有被更改强戴,所以其打印出來的結(jié)果就是'I'm angela'亭螟。事實(shí)上,也用這樣的方法來消解this在回調(diào)函數(shù)中不堪使用的'特色'骑歹。

$("#btn").click(function(){ 
    if(window == this){
        alert("window == this");
    }else{
        alert("window != this")  //彈出來
    }
    alert(this.name); // button
    angela.say();    //I'm  angela
});

將this指向的對象保存到變量(一般用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)錯: undefined is not a function (evaluating 'this.say()')  
            }
        }
    };
    mydemo.init();

第三方庫or框架中的this

比如预烙,使用backbone框架中events時(shí)間回調(diào)中的this,其指向的就是對應(yīng)的視圖道媚,而不是Dom元素扁掸,因?yàn)樵摶卣{(diào)時(shí)通過events哈希綁定的翘县,實(shí)質(zhì)上也是自對應(yīng)視圖那里callback到對應(yīng)的函數(shù);

Javascript中的eval 方法

JavaScript 中的 eval 方法可以將字符串轉(zhuǎn)換為 JavaScript 代碼,使用 eval 方法時(shí)谴分,this 指向哪里呢锈麸?答案很簡單,看誰在調(diào)用 eval 方法牺蹄,調(diào)用者的執(zhí)行環(huán)境(ExecutionContext)中的 this 就被 eval 方法繼承下來了忘伞。(悪,還沒用過,有待實(shí)踐下)沙兰!
但是:在嚴(yán)格模式之下氓奈,eval的作用域也被改變了。正常模式下鼎天,eval語句的作用域舀奶,取決于它處于全局作用域,還是處于函數(shù)作用域斋射。嚴(yán)格模式下育勺,eval語句本身就是一個(gè)作用域,不再能夠生成全局變量了罗岖,它所生成的變量只能用于eval內(nèi)部涧至。

 "use strict";
  var x = 2;
  console.info(eval("var x = 5; x")); // 5
  console.info(x); // 2

后記:由于javascript的動態(tài)性(解釋執(zhí)行,當(dāng)然也有簡單的預(yù)編譯過程)呀闻,this的指向在運(yùn)行時(shí)才確定化借,因此在只要足夠留心其運(yùn)行時(shí)的上下文,即可無痛揮霍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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蓖康,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子垒手,更是在濱河造成了極大的恐慌蒜焊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件科贬,死亡現(xiàn)場離奇詭異泳梆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)榜掌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門优妙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憎账,你說我怎么就攤上這事套硼。” “怎么了胞皱?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵邪意,是天一觀的道長九妈。 經(jīng)常有香客問我,道長雾鬼,這世上最難降的妖魔是什么萌朱? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮策菜,結(jié)果婚禮上晶疼,老公的妹妹穿的比我還像新娘。我一直安慰自己又憨,他們只是感情好冒晰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著竟块,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耐齐。 梳的紋絲不亂的頭發(fā)上浪秘,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音埠况,去河邊找鬼耸携。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辕翰,可吹牛的內(nèi)容都是我干的夺衍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼喜命,長吁一口氣:“原來是場噩夢啊……” “哼沟沙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起壁榕,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤矛紫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后牌里,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颊咬,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年牡辽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喳篇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡态辛,死狀恐怖麸澜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情因妙,我是刑警寧澤痰憎,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布票髓,位于F島的核電站,受9級特大地震影響铣耘,放射性物質(zhì)發(fā)生泄漏洽沟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一蜗细、第九天 我趴在偏房一處隱蔽的房頂上張望裆操。 院中可真熱鬧,春花似錦炉媒、人聲如沸踪区。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缎岗。三九已至,卻和暖如春白粉,著一層夾襖步出監(jiān)牢的瞬間传泊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工鸭巴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眷细,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓鹃祖,卻偏偏與公主長得像溪椎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子恬口,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容