說到this,就不得不提到function,相信看過其它相似文章的同窗也知道,正是因爲調用function的對象不一樣,才致使了this的指向不一樣。因此之前總是去記憶每種調用function的狀況所對應的this,由於狀況有限並且不多,因此這固然是可行的——對於聰明人來講。因此我不得不思考另一些方式來讓我記住。javascript
那麼首先咱們須要明確的一個事情是:___function___也是對象java
同時咱們還須要明確的一個事情是: function執行時是在某個特定的上下文中執行的。瀏覽器
全局環境是最外圍的一個執行環境。全局執行環境被認爲是window對象。所以全部全局變量和函數都是做爲window對象的屬性和方法建立的。代碼載入瀏覽器時,全局執行環境被建立(當咱們關閉網頁或者瀏覽器時全局執行環境才被銷燬)。好比在一個頁面中,第一次載入JS代碼時建立一個全局執行環境。這也是爲何閉包有一個內存泄露的缺點。由於閉包中外部函數被當成了全局環境。因此不會被銷燬,一直保存在內存中。markdown
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數環境就會被推入一個環境棧中。當函數執行完以後,棧將其環境彈出,把控制權返回給以前的執行環境。函數執行環境的變量對象是該函數的活動對象(activation object)。這就是上下文,函數執行時它也須要一些額外的信息來支撐它的運行。那麼既然function是對象的話,就會有方法。而function中最核心的方法是call方法。所以咱們就從這兒入手。閉包
先來看一下如何使用call方法:app
function say(content) {
console.log("From " + this + ": Hello "+ content);
}
say.call("Bob", "World"); //==> From Bob: Hello World
複製代碼
上面例子中,咱們經過call方法,讓say函數執行時的this指向Bob,而後把World做爲參數傳進去,因此輸出結果是能夠預見的。函數
js執行函數時會默認完成以上的步驟,你能夠把直接調用函數理解爲一種語法糖 好比:oop
function say(word) {
console.log(world);
}
say("Hello world");
say.call(window, "Hello world");
複製代碼
以上能夠把say("Hello world") 看作是 say.call(window,"Hello world") 的語法糖。 這個結論很是關鍵 因此之後每次看見functionName(xxx) 的時候,你須要立刻在腦海中把它替換爲functionName.call(window,xxxx),這對你理解this的指向很是重要。不過也有例外,在ES5的strict mode中call的第一個參數不是window而是undefined。以後的例子我假設老是不在strictmode下,但你須要記住strictmode有一點兒不一樣
。ui
對於匿名函數來講,上面的結論也是成立的this
(function(name) {
//
})("aa");
//等價於
(function(name) {
//
}).call(window, "aa");
複製代碼
函數做爲對象的方法被調用 直接來看代碼:
var person = {
name : "caibirdme",
run : function(time) {
console.log(this.name + "has been running for over "+ time+ " minutes");
}
};
person.run(30); //==> caibirdme has been running for over 30 minutes
//等價於
person.run.call(person, 30); // the same
複製代碼
你會發現這裏call的第一個參數是person而不是window。 當你明白了這兩點,下意識地把函數調用翻譯成foo.call() 的形式,明確call的第一個參數,那基本上this的問題就難不住你了。
function hello(thing) {
console.log(this + " says hello " + thing);
}
person = { name: "caibirdme" }
person.hello = hello;
person.hello("world") // 至關於執行 person.hello.call(person, "world")
//caibirdme says hello world
hello("world") // 至關於執行 hello.call(window, "world")
//[object DOMWindow]world
複製代碼
var obj = {
x: 20,
f: function(){ console.log(this.x); }
};
obj.f(); // obj.f.call(obj)
//==> 20
obj.innerobj = {
x: 30,
f: function(){ console.log(this.x); }
}
obj.innerobj.f(); // obj.innerobj.f.call(obj.innerobj)
// ==> 30
複製代碼
var x = 10;
var obj = {
x: 20,
f: function(){
console.log(this.x); //this equals obj
// ==> 20
var foo = function(){ console.log(this.x); }
foo(); // foo.call(window)
//foo中this被指定爲window,因此==> 10
}
};
obj.f(); // obj.f.call(obj)
// ==> 20 10
複製代碼
由例三引出一個很是common的問題,若是我想讓foo輸出20怎麼辦?這時候須要用到一點小技巧
var x = 10;
var obj = {
x: 20,
f: function(){
console.log(this.x);
var that = this; //使用that保留當前函數執行上下文的this
var foo = function(){ console.log(that.x); } //此時foo函數中的this仍然指向window,但咱們使用that取得obj
foo(); // foo.call(window)
}
};
obj.f(); obj.f.call(obj)
// ==> 20 20
複製代碼
再來一個稍微難一點點的(但其實用call替換法一點兒也不難)
var x = 10;
var obj = {
x: 20,
f: function(){ console.log(this.x); }
};
obj.f(); // obj.f.call(obj)
// ==> 20
var fOut = obj.f;
fOut(); // fOut.call(window)
//==> 10
var obj2 = {
x: 30,
f: obj.f
}
obj2.f(); // obj2.f.call(obj2)
//==> 30
複製代碼
例五若是沒有明確:
es5中this是在執行是纔會被確認的
是會出錯的。 可能會認爲說 obj.f 那個函數定義在obj裏面,那 this 就該指向obj。
先看一段代碼:
func person(name) {
this.name = name;
}
var caibirdme = new person("deen");
// caibirdme.name == deen
複製代碼
函數在用做構造函數時一樣能夠用call方法去代替,那這裏怎麼代替呢? 這裏又須要明確一點:
new constrcut()是一種建立對象的語法糖
它等價於:
function person(name) {
this.name = name;
}
var foo = new person("deen");
//經過new建立了一個對象
//new是一種語法糖,new person等價於
var bar = (function(name) {
var _newObj = {
constructor : person,
__proto__ : person.prototype,
};
_newObj.constructor(name); // _newObj.constructor.call(_newObj, name)
return _newObj;
})();
複製代碼
new的時候this就指向新的對象 總結來講就是下面兩個等價變形:
只要理解以上兩個變形,this就再也不是問題啦!!