1. 程式人生 > >閉包中this的指代,以及四種函式呼叫中this的指代

閉包中this的指代,以及四種函式呼叫中this的指代

    Q:

  1. // (1)這裡this指代什麼?
  2. function a(){
  3.     // (2)這裡this指代什麼?
  4.     var c = 2;
  5.     function b(){
  6.         // (3)這裡呢?this又指代什麼?
  7.         return c;
  8.     }
  9.     return b;
  10. }
  11. a()();  //呼叫a()函式會返回一個b函式,a()()表示當返回b函式之後立即執行。
A:(1)處指window,(2)處指的是a,(3)處指的是b。
面試官:嗯?是嗎?你不好好想想嗎?

我的內心:啊啊啊,哪裡有洞。。。我是有多菜。

是,你沒有看錯,我確實是這麼回答的,很可笑啊,我一直都覺得這真的是恥辱啊,我想我一輩子都不會忘記。不過,這個面試官很nice,給我道出了正確答案,還告訴我為什麼。這場面試下來,我所有的自信心都被摧毀得破碎不堪,但它卻是我最有收穫的一次面試,也成為我前端路上的一次轉折點。
        正確的應該是:這三處均指的是window物件。

       This,何意?這,這裡,這個。在JavaScript中,它是關鍵字,既不是變數,也不是屬性名,所以它是不允許被賦值的。那它是什麼關鍵字呢?在JavaScript中,它指的是函式呼叫的上下文(context)。
        根據ECMAScript3和非嚴格的ECMAScript5對函式呼叫的規定,this的值是全域性物件。在嚴格模式下,則是undefined。和變數不同,關鍵字沒有作用域的限制,巢狀函式(閉包)不會從呼叫它的函式中繼承this。若想訪問外部函式的this值,需要將它的值儲存在一個變數裡,這個變數和內部函式都同在一個作用域內。可看下面程式碼:

  1. var o = {
  2.     m
    : function(){
  3. var self = this;
  4. console.log(this === o); // true, this就是這個物件o
  5. f();
  6. // 定義一個巢狀函式f()
  7. function f(){
  8.     console.log(this === o); // false,this的值是全域性物件或者undefined
  9.     console.log(self === o); // true, self值外部函式的this值
  10. }
  11.     }
  12. };
  13. o.m();

       this是在執行時基於函式的執行環境繫結的。在全域性函式中,this等於window,而當函式被作為某個物件的方法呼叫時,this等於那個物件。不過,隨著函式使用場合不同,this的值可能發生變化。我們知道,在JavaScript中,呼叫函式有4種方式:

  • 普通的函式呼叫
  • 作為物件的方法呼叫
  • 作為建構函式呼叫
  • 通過它們的call()和apply()間接呼叫

那麼就從它的呼叫方式簡單分析一下,如有錯誤,煩請批評指正:

(1)作為函式

這是最通常的函式用法,屬於全域性性呼叫,因此this等於全域性物件。

  1. function f(){
  2.     window.x = 1;
  3.     console.log(this.x);
  4. }
  5. f(); // 1

對程式碼做些改變再看,

  1. var x = 1;
  2. function f(){
  3.     console.log(this.x);
  4. }
  5. f(); // 1
  6. console.log(window.x);// 1

window.x與this.x值相等。再變一下,

  1. var x = 1;
  2. function f(){
  3.     this.x = -1;
  4. console.log(this.x);
  5. }
  6. f(); // -1
  7. console.log(x); // -1

在f()中給this.x賦值為-1,結果x的值跟著改變為-1,所以this等於全域性物件。

(2)作為方法

當函式作為某個物件的方法呼叫時,這時this的值就是這個物件。

  1. function f(){
  2.     console.log(this.x);
  3. }
  4. var o = {
  5.     x: -1,
  6.     m: f
  7. };
  8. o.m(); // -1
  9. o.x = 1;
  10. o.m(); // 1

物件的屬性值改變,f()裡面this.x跟著改變。可以在f()裡面列印一下this,看看結果呢!

(3)作為建構函式

作為建構函式呼叫時,使用new關鍵字初始化一個例項物件,這時this等於這個例項物件。

  1. function f(){
  2.     this.name = "zrn";
  3. }
  4. var o = new f();
  5. console.log(o.name); // "zrn"

執行結果為”zrn”,表示這並非全域性物件。變一下程式碼,

  1. var name = "window";
  2. function f(){
  3.     this.name = "zrn";
  4. }
  5. var o = new f();
  6. console.log(o.name); // "zrn"
  7. console.log(name); // "window"

name的值互不干涉。

(4)通過call()或者apply()呼叫

call()和apply()的用途都是在特定的作用域中呼叫函式,實際上等於設定函式體內this的值。第一個引數(context)是函式執行的作用域,第二個引數是引數陣列(argArr),不過使用call()時,要將這些引數逐個列舉出來。如果未傳遞context,則context是全域性物件(啊,說的有點多了)。

  1. var name = "window";
  2. function f(){
  3.     console.log(this.name);
  4. }
  5. var o = {name: "zrn"};
  6. f.call(); // "window", this指的是window
  7. f.apply(o); // "zrn", this指的是物件o
結束語:以上就是我對this的基礎理解了,不過理解與正確地使用又差一大截,所以只有不斷地實踐,不斷地積累,才能夠對this的使用信手拈來,達到爐火純青的地步,所以,come on!
ps: call()和apply()方法是很強大的方法,期待更深入的學習它們。