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() 之后函數並沒有執行,可以傳給其他函數,在某個適當的時機再調用。