JavaScript 之 this 探究

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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