JavaScript:this學習

在函數(shù)執(zhí)行時,this 總是指向調用該函數(shù)的對象。要判斷 this 的指向,其實就是判斷this 所在的函數(shù)是誰在調用。至于函數(shù)在哪里定義,定義的方式(是聲明方式還是表達式方式),都無關緊要。

  • 有對象就指向調用對象
  • 沒調用對象就指向全局對象
  • 用new構造就指向新對象
  • 通過 applycallbind 來改變 this 的所指。

this指向調用的對象

  • 在何處或者如何定義調用函數(shù)完全不會影響到this的行為。誰調用函數(shù)才是關鍵。
  • 當對象嵌套時,this指向的是調用函數(shù)的那一個對象。至于外面嵌套的,以及里面被嵌套的,都不會去查找。
    比如下面的例子:
    b通過屬性g()調用函數(shù)independent時,this就指向b。這時候b只管自己,一看沒找到指定屬性prop,就輸出undefined。他不管外面的o或者里面的c有沒有要找的屬性prop;他不會去找的。
function independent() {
  return this.prop;   // 函數(shù)全局定義,但是this要調用的時候才能確定,現(xiàn)在不知道
}

var o = {
    prop: 37,
    f: independent,
    b: {
        g: independent,
        c: {
            prop: 50, 
            h: independent
        }
    }
};

// 這里的調用者是o,所以this指向o
console.log(o.f()); // logs: 37

// 這里的調用者是b,所以this指向b;對于外面的o,以及里面嵌套的c,都不可見
console.log(o.b.g()); // logs: undefined

// 這里的調用者是c,所以this指向c 
console.log(o.b.c.h());  // logs: 50
  • 原型鏈中的 this指向的也是調用函數(shù)的對象,至于函數(shù)是在子類中定義的還是在父類中定義的,無關緊要。
    比如下面的例子:
    子類p中的函數(shù)f是從父類o中繼承來的。當p調用函數(shù)f的時候,this就指向p
var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
// 原型鏈:p --> o --> Object.prototype --> null
var p = Object.create(o);
p.a = 1;
p.b = 4;

// 調用者是p,this指向p
console.log(p.f()); // 5

// 調用者是o,this指向o;o沒有a和b,所以輸出NaN
console.log(o.f()); // NaN

this指向全局對象

  • 在全局運行上下文中(在任何函數(shù)體外部),this指代全局對象
console.log(this.document === document); // true

// 在瀏覽器中,全局對象為 window 對象:
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37
  • 在函數(shù)中使用this,不過調用函數(shù)的地方是全局的,此時this就是指向全局的。
function f1() {
    return this;
}
// 全局調用,this指向全局,在瀏覽器下是window
f1() === window; // true
  • 如果找不到調用函數(shù)的具體對象,(不是函數(shù)的作用域),那么默認就是全局對象。
function fn1() {
    var a = 2;
    fn2();
}
function fn2() {
    console.log( this.a );
}
// 調用fn2的是函數(shù)fn1,找不到具體的對象,所以fn2中的this指向全局對象window
fn1(); // log: undefined

this應該指向一個具體的對象,或者全局對象,比如window,跟函數(shù)的作用域{}一點關系沒有。另外,this的確定是在運行時,而不是在定義的時候。所以哪個對象調用了函數(shù)才是確定this到底指向哪個對象的關鍵點。
下面代碼對this的理解和使用都是錯誤的,應該避免這種用法

function fn1() {
    var a = 2;
    this.fn2(); // 以為this引用的是fn1的詞法作用域是錯誤的,這里的this要刪除,沒有作用。
                // 另外這里是函數(shù)定義,this的具體指向不確定。比如本例中,下面代碼調用fn1的是全局對象window,所以this指向了全局對象window
}
function fn2() {
    console.log( this.a );
}
// 調用fn2的是函數(shù)fn1,找不到具體的對象,所以fn2中的this指向全局對象window
// 調用fn1的是全局對象window,所以fn1中的this指向全局對象window
fn1(); // log: undefined
  • 失去隱式綁定的情況: 如果找不到具體的調用對象,就會指向默認的全局對象。下面這種情況也是需要注意區(qū)分的:
function fn() {
    console.log( this.a );
}

var a = "全局"; // 定義全局變量
var obj = {
    a: "obj內部", // 定義內部變量
    fn: fn
};
var bar = obj.fn; // 函數(shù)引用傳遞; 其實就是bar = fn; ==> bar和obj.fn都指向了fn

// 相當于fn(); 而不是obj.fn(); 調用者是全局,所以this指向全局
bar(); // "全局"
// 調用者是obj,所以this指向obj
obj.fn(); // "obj內部"

構造函數(shù)中的this

  • 當一個函數(shù)被作為一個構造函數(shù)來使用(使用new關鍵字),它的this與即將被創(chuàng)建的新對象綁定。
function C(){
  this.a = 37;   // 這里只是函數(shù)定義,還沒有運行,this指誰還不確定
}
// 構造過程就是構造函數(shù)的執(zhí)行,已經是運行時,(函數(shù)調用也是運行時了),this可以確定。這里和o綁定,this指向o
var o = new C();
console.log(o.a); // logs 37
  • js 中,構造函數(shù)、普通函數(shù)、對象方法、閉包,這四者沒有明確界線。如果使用不當,構造函數(shù)就會改變其原來的意義。
    比如下面這種用法就強烈不推薦使用:
function C2(){
  this.a = 37;
  // 這里手動返回了對象,默認的this被取消;
  // new出來的對象和返回的這個對象綁定,而不是跟this。
  // 這就導致this.a = 37;成了“僵尸”代碼。執(zhí)行了,但是外界根本拿不到
  return {a:38};
}
// 這里o和對象{a:38}綁定,而不是this
o = new C2();
console.log(o.a); // logs 38
  • 構造函數(shù)功能和其他語言中的類差不多,ES6引入的class關鍵字,就可以看做是構造函數(shù)改頭換面而來的。
    在構造函數(shù)的使用上,需要遵循一定的規(guī)范,充分發(fā)揮其本職功能。到ES6切換到class關鍵字,也就水到渠成。
    (1)定義用聲明形式。不要用表達式形式。
    表達式格式的函數(shù)定義方式是為了讓函數(shù)成為變量,可以當做參數(shù),返回值等使用,這給函數(shù)式編程帶來了很大的方便。這個適合普通函數(shù),對于構造函數(shù),強烈不推薦。
    (2)構造函數(shù)的命名采用大駝峰式,和類的命名習慣一致
    (3)構造函數(shù)不要有返回值
    (4)函數(shù)或者共享變量,定義在構造函數(shù)的原型上,節(jié)省內存;定義的方式應該在構造函數(shù)的外面。構造函數(shù)名.prototype = ...;

通過 applycallbind 來改變 this 的所指

  • 當一個函數(shù)的函數(shù)體中使用了this關鍵字時,通過所有函數(shù)都從Function對象的原型中繼承的call()方法和apply()方法調用時,它的值可以綁定到一個指定的對象上。
function add(c, d){
  return this.a + this.b + c + d;
}

var o = {a:1, b:3};

// The first parameter is the object to use as 'this', subsequent parameters are passed as 
// arguments in the function call
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16

// The first parameter is the object to use as 'this', the second is an array whose
// members are used as the arguments in the function call
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
  • 使用 callapply 函數(shù)的時候要注意,如果傳遞的this值不是一個對象,JavaScript 將會嘗試使用內部 ToObject操作將其轉換為對象。
    因此,如果傳遞的值是一個原始值比如 7或 'foo' ,那么就會使用相關構造函數(shù)將它轉換為對象,所以原始值7通過new Number(7)被轉換為對象,而字符串'foo'使用 new String('foo')轉化為對象,例如:
function bar() {
  console.log(Object.prototype.toString.call(this));
}
bar.call("foo");  // [object String]
bar.call(7);      // [object Number]
  • ECMAScript 5 引入了 Function.prototype.bind
    。調用f.bind(someObject)會創(chuàng)建一個與f具有相同函數(shù)體和作用域的函數(shù),但是在這個新函數(shù)中,this將永久地被綁定到了bind的第一個參數(shù),無論這個函數(shù)是如何被調用的。
function f(){
  return this.a;
}

// function g 與對象{a:"azerty"}綁定,并且不變
var g = f.bind({a:"azerty"}); 
// 全局調用g,this仍然指向{a:"azerty"}
console.log(g()); // azerty
// 對象o調用f,this指向o;對象o調用g,this仍然指向{a:"azerty"}
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty

內部函數(shù)

  • 在內部函數(shù)中,this沒有按預想的綁定到外層函數(shù)對象上,而是綁定到了全局對象。這里普遍被認為是JavaScript語言的設計錯誤,因為沒有人想讓內部函數(shù)中的this指向全局對象。
var name = "global";  

var person = {  
    name : "person",  
    hello : function(sth) {  
        var sayhello = function(sth) {  
            console.log(this.name + " says " + sth);  
        };  
        sayhello(sth);  
    }  
}   
person.hello("hello world"); // global says hello world 
  • 一般的處理方式是將this作為變量保存下來,一般約定為that或者self
var name = "global";  

var person = {  
    name : "person",  
    hello : function(sth) {  
        var that = this; 
        var sayhello = function(sth) {  
            console.log(that.name + " says " + sth);  
        };  
        sayhello(sth);  
    }  
}   
person.hello("hello world"); // person says hello world 

參考文章

this
這篇文章的例子挺不錯的

javascript中this的四種用法
開頭總結的幾句話不錯,方便理解

詳解 JavaScript 中的 this
內部函數(shù)的例子來自這里,不過格式錯誤,要稍微改一下

別再為了this發(fā)愁了:JS中的this機制
這篇文章中,那個this不是指函數(shù)作用域{}的例子很好。
this跟函數(shù)在哪里定義沒有半毛錢關系,函數(shù)在哪里調用才決定了this到底引用的是啥。”這句話也講得非常到位。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容