1. 程式人生 > >學會JS的this這一篇就夠了,根本不用記

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

以前看某本書上講:

掌握了JS中this的用法才算真正的跨過了JS的門檻

我深以為是!但是JS的this卻並不是那麼簡單的內容,許多同學都很困惑,也肯定有同學像我曾經一樣查閱各種資料想知道How to understand this of javascript。很幸運的是,網上有非常非常多的文章關於this的,簡直就是琳琅滿目…曾經,我看到某些文章非常開心,因為他們講得確實非常好,以至於我確信我已掌握了this的用法。然而可能是由於我太笨了,經常過一段時間就忘得一乾二淨了,這真的是一件很尷尬的事情……直到後來我仔細地琢磨又琢磨,終於感覺我可能以後不會再忘記了。所以想把我琢磨的內容和大家分享一下

說到this,就不得不提到function,相信看過其它類似文章的同學也知道,正是由於呼叫function的物件不同,才導致了this的指向不同。所以以前老是去記憶每種呼叫function的情況所對應的this,因為情況有限而且很少,所以這當然是可行的——對於聰明人來說。所以我不得不思考另外一些方式來讓我記住。

那麼首先我們需要明確的一個事情是:function也是物件 同時我們還需要明確的一個事情是:function執行時是在某個特定的上下文中執行的。那什麼是上下文呢?比如你要console.log(a),沒有a就無法支援函式的執行,所以某種意義上講,a就是上下文的一部分。函式執行時它也需要一些額外的資訊來支撐它的執行。 既然function

是物件的話,就會有方法。而function中最核心的方法是call方法,這也是理解this最關鍵的方法,因此我們就從這兒入手。

call方法

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

 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執行函式時會預設完成以上的步驟,你可以把直接呼叫函式理解為一種語法糖 比如

    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有一點兒不同。 對於匿名函式來說,上面的結論也是成立的

    (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

例五有些同學會可能出錯的原因,是沒有明確我上面說的: 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;
    })();

So you can see……為什麼new的時候this就指向新的物件了吧?

通過我這篇文章,我希望學會通過把一個函式呼叫替換成funcName.call的形式,從而理解執行時上下文中this到底指向誰。總結來說就是下面兩個等價變形:

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

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

只要理解以上兩個變形,this就不再是問題啦!!希望我的這種方法對各位同學認識this有所幫助,不要再像我曾經一樣掉入this的坑中,相關面試題也不再怕怕啦,哈哈。如果還有問題的話,歡迎下面留言。

作者:PHPBird 連結:http://www.imooc.com/article/1758 來源:慕課網