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