深入淺出js中的this

Q:this是什麼?

A:this是Javascript語言的一個關鍵字,它代表函數運行時,自動生成的一個內部對象,在每個 function 中自動根據作用域(scope) 確定, 指向的是此次調用者。

 

Q:this的使用場景?

A:  1.普通函數調用。

  2.作爲對象的方法來調用。

  3.作爲構造函數調用。

  4.函數被call,apply,bind調用的時候。

 

栗子:

普通函數調用

function test1(){
  console.log(this);
}
test1();   // window

在非嚴格模式下,由於this必須是一個對象,所以就默認爲指向全局對象window。在嚴格模式下,上面的代碼則會出現不同的結果,如下:

function test1(){
  "use strict";
  console.log(this); 
} 
test1(); // undefined

再看看下面這段代碼:

function test2(){
    this.x=2;
}
test2();
console.log(window.x);   // 2

由於普通函數的調用,this的值指向window。所以執行this.x的時候相當於執行了window.x,故window.x的值爲2。

 

作爲對象的方法來調用

var message = {
  content: "I'm a message!",
  showContent: function() {
    console.log(this.content);
  }
};

message.showContent();   // I'm a message!

  上面栗子是將函數保存爲對象的屬性, 這樣就轉化爲一個方法, 可以通過對象調用這個方法。而當函數被當成對象的方法來調用時, 裏面的 this 值就被設置爲調用方法的對象。其實,不管被調用函數在聲明時是屬於方法,還是函數,當最終作爲對象的方法來調用時,this都是指向方法的調用者,即母體對象。看看下面的栗子你就明白了。

var obj = {
    x: 1,
    showX: function() {
        console.log(this.x)
    }
}
obj.showX();  // 1

var obj1={x:11};
obj1.showX=obj.showX;
obj1.showX();  // 11


show=function(){
    console.log('show'+this.x);    
}

obj1.showX=show;
obj1.showX();   // show11

  上面栗子中,在obj對象中,匿名函數在聲明時就作爲obj對象的一個屬性,this直接指向obj對象。函數show在聲明時是一個全局函數,函數裏面的this指向window,但當最後作爲對象obj1的一個屬性時,其this便指向了對象obj1。但是,請注意,並不是所有函數作爲對象的方法來調用時,this都會指向調用者,如下:

var obj = {
    x : 100,
    y : function(){
        setTimeout(
            function(){ console.log(this.x); }    
         , 1000);
    }
};

obj.y();  // undefined

上面栗子中的this指向的是window對象,並不是我們期待的obj,所以會彈出undefined。所以在書寫這類代碼時,儘量避免這種寫法,當然,你也可以在調用的時候將當時的this所指向的對象存在一個變量裏,等到定時器運行時再用所存的變量去調用x,如下:

var obj = {
    x : 100,
    y : function(){
        var that = this;
        setTimeout(
            function(){ console.log(that.x); }
         , 1000);
    }
};

obj.y();   // 100

 

作爲構造函數調用

當一個函數作爲構造器使用時(通過new關鍵字),它的this值綁定到新創建的那個對象。

function Message(content){
   this.content = content;
   this.showContent = function(){
       console.log(this.content);
   };
}

var message = new Message("I'm a message!");
message.showContent();   // I'm a message!

 再看看下面這個栗子。

function obj(){
    this.a=666;
    return 'abc';
}

var obj=new obj();
console.log(obj);  // obj{a: 666} 

有木有看出什麼?當一個帶有return返回值的函數作爲構造器去new一個新的對象時,return的值是被忽略的。是不是很神奇!

 

函數被call,apply,bind調用的時候

所有的函數都有apply()和call()這兩個方法。我們可以通過這兩個方法來改變函數的上下文, 在任何時候都有效, 用來顯式地設置this的值。

apply()方法接收兩個參數: 第一個是要設置爲this的那個對象,第二個參數是可選的,如果要傳入參數,則封裝爲數組作爲apply()的第二個參數即可。

call()方法和apply()基本上是一樣的,除了後面的參數不是數組,而是分散開一個一個地附加在後面。

function warrior(speed, strength){
   console.log(
      "Warrior: " + this.kind +
      ", weapon: " + this.weapon +
      ", speed: " + speed +
      ", strength: " + strength
  );
}

var warrior1 = {
   kind: "ninja",
   weapon: "shuriken"
};

var warrior2 = {
   kind: "samurai",
   weapon: "katana"
};

warrior.call(warrior1, 9, 5);   // Warrior: ninja, weapon: shuriken, speed: 9, strength: 5
warrior.apply(warrior2, [6, 10]);   // Warrior: samurai, weapon: katana, speed: 6, strength: 10

  在上面,我們通過對構造器warrior()傳入不同的參數創建不同類型的對象, this將指向我們通過call() 和/或 apply()傳入的對象。

  在第一個函數調用中,我們使用call() 方法來將this設置爲warrior1對象, 並傳入需要的其他參數, 參數間用逗號分隔。在第二個函數調用中, 其實都差不多, 只是傳入的是warrior2對象, 並將必要參數封裝爲一個數組。

  除了call()和apply()以外,ECMAScript 5還增加了bind()方法,在調用一個函數或方法時也可以通過bind方法來綁定this對象。讓我們看下面的栗子:

function warrior(kind){
   console.log(
      "Warrior: " + kind +
      ". Favorite weapon: " + this.weapon +
      ". Main mission: " + this.mission
   );
}

var attributes = {
   weapon: "shuriken",
   mission: "espionage"
};

var ninja = warrior.bind(attributes, "ninja");

ninja();   // Warrior: ninja. Favorite weapon: shuriken. Main mission: espionage

在這個栗子中, bind()方法的使用方式還是類似的, 但warrior.bind()創建了一個新的函數(方法體和作用域跟warrior()一樣),並沒有改動原來的warrior()函數。新函數的功能和老的一樣, 只是綁定到了attributes對象。

注:bind方法和call,apply的區別在於,bind() 之後函數並沒有執行,可以傳給其他函數,在某個適當的時機再調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章