JavaScript 之 this 探究

JavaScript作為一種腳本語言身份的存在,因此被很多人認(rèn)為是簡單易學(xué)的。然而情況恰恰相反,JavaScript支持函數(shù)式編程、閉包、基于原型的繼承等高級功能。由于其運行期綁定的特性,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 年初,就有開始利用閑余時光,打磨個人最新作品——「傾城之鏈」 ,有意將其打造成優(yōu)良開放型平臺,旨在云集全球優(yōu)秀網(wǎng)站,讓您更為便捷地探索互聯(lián)網(wǎng)中那更廣闊的世界;在這里,您可以輕松發(fā)現(xiàn)學(xué)習(xí)、分享更多有用有趣的事物。目前仍在不斷迭代、優(yōu)化中,如果您對此感興趣,不妨先嘗試一下: 「傾城之鏈」;亦十分歡迎提出您寶貴意見或建議。 (Upade@2018-01-23 于深圳.南山)。也可以通過微信,掃描如下「小程序碼」訪問體驗。

傾城之鏈 - 小程序

全局的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)格模式禁止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 的方法。這個方法的 this 指向這個對象,在這里即對象 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 臨時添加了方法屬性。這樣這個方法中的 this 同樣也指向這個對象 o。

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

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

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

對于內(nèi)部函數(shù),即聲明在另外一個函數(shù)體內(nèi)的函數(shù),這種綁定到全局對象的方式會產(chǎn)生另外一個問題。以下面moveTo方法為例,內(nèi)定義兩個函數(shù),分別將 x,y 坐標(biāo)進行平移。結(jié)果可能出乎大家意料,不僅 point 對象沒有移動,反而多出兩個全局變量 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è)計缺陷,正確的設(shè)計方式是內(nèi)部函數(shù)的this應(yīng)該綁定到其外層函數(shù)對應(yīng)的對象上,為了規(guī)避這一設(shè)計缺陷,聰明的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() 的時候是調(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() 的時候,MyClass()中的 this 會指向一個空對象,這個對象的原型會指向 MyClass.prototype。MyClass()沒有返回值或者返回為基本類型時,默認(rèn)將 this 返回。

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

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

因為返回了對象,將這個對象作為返回值

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)用時,仍然會按綁定時的內(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”)元素上。這個函數(shù)被完整復(fù)制到onclick屬性(現(xiàn)在成為了函數(shù))。因此如果這個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)常犯的一個錯誤,為了避免這種錯誤,許多JavaScript框架都提供了手動綁定 this 的方法。比如Dojo就提供了lang.hitch,該方法接受一個對象和函數(shù)作為參數(shù),返回一個新函數(shù),執(zhí)行時this綁定到傳入的對象上。使用 Dojo,可以將上面的例子改為:

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

其實在我們使用比較多的jQuery也提供了對應(yīng)的解決方案:jQuery.proxy(function, scope).返回一個新函數(shù),并且這個函數(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" ) );
//強制設(shè)置函數(shù)的作用域,讓this指向obj而不是#test對象。

// 以下代碼跟上面那句是等價的:
// $("#test").click( jQuery.proxy( obj.test, obj ) );

// 可以與單獨執(zhí)行下面這句做個比較。
// $("#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ù)指向的即是Html屬性。因此所調(diào)用的函數(shù)(比如angela.say())this上下文沒有被更改,所以其打印出來的結(jié)果就是'I'm angela'。事實上,也用這樣的方法來消解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();  //這兒報錯: undefined is not a function (evaluating 'this.say()')  
            }
        }
    };
    mydemo.init();

第三方庫or框架中的this

比如,使用backbone框架中events時間回調(diào)中的this,其指向的就是對應(yīng)的視圖,而不是Dom元素,因為該回調(diào)時通過events哈希綁定的,實質(zhì)上也是自對應(yīng)視圖那里callback到對應(yīng)的函數(shù);

Javascript中的eval 方法

JavaScript 中的 eval 方法可以將字符串轉(zhuǎn)換為 JavaScript 代碼,使用 eval 方法時,this 指向哪里呢?答案很簡單,看誰在調(diào)用 eval 方法,調(diào)用者的執(zhí)行環(huán)境(ExecutionContext)中的 this 就被 eval 方法繼承下來了。(悪,還沒用過,有待實踐下)!
但是:在嚴(yán)格模式之下,eval的作用域也被改變了。正常模式下,eval語句的作用域,取決于它處于全局作用域,還是處于函數(shù)作用域。嚴(yán)格模式下,eval語句本身就是一個作用域,不再能夠生成全局變量了,它所生成的變量只能用于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的指向在運行時才確定,因此在只要足夠留心其運行時的上下文,即可無痛揮霍this的強大。

原文鏈接: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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容