ES5中令我頭暈的this

說在前面

說到this,就不得不提到function,相信看過其它相似文章的同窗也知道,正是因爲調用function的對象不一樣,才致使了this的指向不一樣。因此之前總是去記憶每種調用function的狀況所對應的this,由於狀況有限並且不多,因此這固然是可行的——對於聰明人來講。因此我不得不思考另一些方式來讓我記住。javascript

那麼首先咱們須要明確的一個事情是:___function___也是對象java

幾條綱領

  • 函數執行時,首先看函數名前是否存在‘.’,存在-> ‘.’前面是誰,this就指向誰;不存在-> this指向window。
  • 自執行函數中的this指向window。
  • 元素綁定事件後,事件執行時,回調函數中的this指向當前事件元素。
  • 構造函數模式中(new Function),類中(函數體中)this.xxx = xxx,this就是當前類的實例。
  • 經過call、apply、bind方法中第一個參數強行改變this指向,call、apply、bind參數中第一個參數就是方法調用者的this指向。當call、apply、bind參數中第一個參數是空、null、undefined時,this指向window。

在原型模式中查找this方法步驟

  1. 首先肯定this的指向(上面5點)
  2. 把this替換成對應代碼塊
  3. 按照原型鏈機制,一步步查找結果

同時咱們還須要明確的一個事情是: function執行時是在某個特定的上下文中執行的。瀏覽器

那什麼是上下文

  • 全局執行環境

全局環境是最外圍的一個執行環境。全局執行環境被認爲是window對象。所以全部全局變量和函數都是做爲window對象的屬性和方法建立的。代碼載入瀏覽器時,全局執行環境被建立(當咱們關閉網頁或者瀏覽器時全局執行環境才被銷燬)。好比在一個頁面中,第一次載入JS代碼時建立一個全局執行環境。這也是爲何閉包有一個內存泄露的缺點。由於閉包中外部函數被當成了全局環境。因此不會被銷燬,一直保存在內存中。markdown

  • 函數執行環境

每一個函數都有本身的執行環境。當執行流進入一個函數時,函數環境就會被推入一個環境棧中。當函數執行完以後,棧將其環境彈出,把控制權返回給以前的執行環境。函數執行環境的變量對象是該函數的活動對象(activation object)。這就是上下文,函數執行時它也須要一些額外的信息來支撐它的運行。那麼既然function是對象的話,就會有方法。而function中最核心的方法是call方法。所以咱們就從這兒入手。閉包

call方法

先來看一下如何使用call方法:app

function say(content) {  
       console.log("From " + this + ": Hello "+ content);  
   }  
say.call("Bob", "World"); //==> From Bob: Hello World 
複製代碼
  • Step1: 把第二個到最後一個參數做爲函數執行時要傳入的參數
  • Step2: 把函數執行時的this指向第一個參數
  • Step3: 在上面這個特殊的上下文中執行函數

上面例子中,咱們經過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就指向新的對象 總結來講就是下面兩個等價變形:

  • · foo() ---> foo.call(window)
  • · obj.foo() --> obj.foo.call(obj)

只要理解以上兩個變形,this就再也不是問題啦!!

學會JS的this這一篇就夠了,根本不用記

相關文章
相關標籤/搜索