1. 程式人生 > 其它 >JavaScript之令人費解的this

JavaScript之令人費解的this

學會使用JavaScript只需要三天,但是想學好JavaScript需要三年。

在js中,最讓人頭疼的存在莫過於,閉包,作用域,this指向問題,以及非同步等等。

可以說:前端人員很多人才開始都沒有真的弄懂this。

this的那些繫結規則 常見的繫結規則有如下四種:

預設繫結 隱式繫結 顯示繫結 new 繫結 接下來讓我們好好說道說道。

預設繫結 什麼是預設繫結?預設繫結不就是什麼處理都不做的時候的繫結規則嗎?很顯然就是我們直接呼叫函式的時候,這時候就是預設繫結。不需要多說,預設繫結的this就是window

全域性呼叫全域性函式 在全域性定義的函式,直接呼叫,很明顯繫結的是window

function foo(){ console.log(this); }

// window foo()

函式內呼叫全域性函式 在全域性定義的函式,如果在其他函式內呼叫,那麼this是繫結的是那個物件?肯定還是window了,畢竟函式還是直接呼叫嘛。

function foo(){ console.log(this); } function bar(){ foo(); } // window bar()

隱式繫結 隱式繫結就是通過物件的形式呼叫函式。其實也可以說是呼叫方法,因為這些函式都是定義在物件上的。當然方法只是函式在不同位置的叫法而已,這裡不需要糾結。

比如我們定義物件obj,並在上面定義方法(函式)foo。那麼通過物件obj呼叫函式foo,繫結的this的值會是????

var obj = { name: "zs", foo: function(){ console.log(this); } } // ? obj.foo(); // obj

當然隱式繫結的this的值是物件obj了。那麼結果顯而易見,隱式繫結的優先順序是大於預設繫結的。

而隱式繫結呼叫的函式的this的指向可以理解為,誰呼叫,this就指向誰。之所以我們說是叫做隱式繫結,是因為我們沒有在函式上直接繫結this的值是xxx物件。

顯示繫結 顯示繫結就是顯示的告訴函式,我要繫結this的值是xxx物件。

全域性函式顯示繫結this 定義的全域性函式,如果在執行的時候,直接顯示的繫結this的值,那麼很顯然,this的值肯定是我們手動繫結上去的嘍!

function foo(){ console.log(this); } foo(); // window foo.call("123"); // '123' foo.apply(123); // 123 foo.bind({name:"zzz"})(); // {name: "zzz"}

結果可以證明: 顯示繫結的this的優先順序是大於預設繫結規則的。

那麼接下來我們應該比較一下顯示繫結和隱式繫結的優先順序。

物件的方法(函式)顯示繫結this 我們定義一個物件,並對該函式的方法進行顯示的繫結,看其中的this是指向哪裡的。

var obj = { name: "zs", foo: function () { console.log(this); } } obj.foo.call("123"); // '123' obj.foo.apply("123"); // '123' obj.foo.bind("123")(); // '123'

通過取出物件obj的方法foo,然後對其進行顯示的this繫結,我們發現結果是:this的指向是我們顯示繫結的物件。

那麼這樣是否就證明了:顯示繫結的優先順序就一定大於隱式繫結?其實不然,我們通過物件.方法名的形式,已經是直接取出了這個定義在方法中的函式的引用,接下來進行該函式的顯示繫結,嚴格來說已經和物件obj沒有關係了。

var obj = { name: "zs", foo: function () { console.log(this); } } var fn = obj.foo; fn(); // window

可以看見,取出了物件中定義的函式,然後在進行呼叫,可以對比函式直接定義在全域性中,然後通過預設繫結規則的呼叫該函式,所以最終this也是繫結在全域性物件window上的。

那麼,我們要如何證明顯示繫結和隱式繫結的優先順序的大小呢?

當然是有辦法的:

我們的目的就是進行顯示的繫結this,且隱式的呼叫這個顯示繫結的函式,這樣最終的this指向,就可以證明出我們兩種方式的優先順序了。

我們實現定義好函式,然後定義物件obj,且將定義好的全域性函式通過顯示的繫結this的值,然後將這個顯示繫結好this的函式賦值給obj物件的foo屬性。最後通過obj.foo()呼叫該函式,就知道顯示繫結和隱式繫結的優先順序了。

function foo(){ console.log(this); } var obj = { name: "zs", foo: foo.bind("123") } obj.foo(); // "123"

根據結果,我們可以知道,顯示繫結this的優先順序肯定是大於隱式繫結的。

new 繫結 new繫結,其實就是通過建構函式建立物件。當我們new的時候,會建立一個空物件,且將空物件的引用賦值給建構函式的this,這樣this就會指向這個空物件,且會在建構函式的最後,如果沒有返回值,自動幫我們返回這個this。

寫成虛擬碼大概就是:

function Person(name,age){ this.name = name; this.age = age; // 不需要我們返回this return this; }

new繫結需要的函式是建構函式,建構函式沒有顯示指定的返回值,且建構函式一般首字元是大寫。

function Person(name,age){ this.name = name; this.age = age; } var person = new Person("zs",21); console.log(person);

new 繫結,其中的this的指向就是我們通過new關鍵字創建出來的空物件。然後通過建構函式給這個空物件新增各種屬性。

特殊的this繫結規則 上面的繫結規則只能說是我們經常遇到的,或者說我們會使用的繫結規則。但是凡事總有例外,總有一些特例對this的繫結讓人感到苦惱,因為瀏覽器在解析它們的時候進行了特殊的處理,接下來介紹一下我知道的那些特例。前端培訓

忽略顯示繫結 我們上面看見了,顯示繫結是大於隱式繫結,也是大於預設繫結規則的。這是毫無疑問的,前面已經證實過,那麼接下來,我說的可能讓你感到疑惑,為什麼瀏覽器又會忽略顯示繫結呢?

正如前面:

function foo(){ console.log(this); } // 預設繫結 直接呼叫 window foo();

var obj = {name:"zs"};

// 顯示繫結 三者都是 obj foo.apply(obj); foo.call(obj); foo.bind(obj)();

**毋庸置疑:**這些結果如我們所預期一般。

但是,當我們進行顯示繫結的時候,如果我們繫結的物件是null,或者是 undefined,那麼我們函式執行時的this也是我們繫結的那個值嗎?

function foo(){ console.log(this); } // 全是 window foo.call(null); foo.apply(null); foo.call(undefined); foo.apply(undefined);

**結果說明了一切,**瀏覽器並不會把 null或者undefined繫結到this上。也就是說,在進行this繫結的時候,會檢視繫結到this上的值,如果值不是null或者undefined才會正常的將物件繫結到this上,否則,顯示繫結的this依然會繫結為window物件

如果既有顯示繫結,又有隱式繫結的情況下,但是顯示繫結的是null或者undefined,結果又會如何呢?

我覺得即使不測試,應該也可以猜到結果。很顯然嘛,顯示繫結是高於隱式繫結的,那麼結果肯定是以顯示繫結為主啊!所以肯定還是 window物件了

var obj = { name:"zs", foo(){ console.log(this) } } // window obj.foo.call(null); obj.foo.apply(undefined); obj.foo.bind(null)();

沒啥毛病,結果就是window物件咯!

間接函式引用 當我們建立一個函式的間接引用,這種情況使用預設繫結規則。

var obj1 = { name: "obj1", foo: function bar() { console.log(this); } }; var obj2 = { name: "obj2" }; (obj2.bar = obj1.foo)(); console.log((obj2.bar = obj1.foo)); var fn = (obj2.bar = obj1.foo); fn();

可以發現:

賦值語句 (obj2.bar = obj1.foo)的結果是一個函式(這個函式可能有名字,也可能沒名字,取決於我們在物件中定義屬性的時候,賦值的函式是否有名字)。其實就是取出了obj1的函式foo,外面加上括號包裹就是將函式作為單獨的執行體進行執行。比如**(obj1.foo)()**的結果和上面的結果是不一致的,this還是隱式繫結為obj1。二者不同是因為前者括號裡面的是賦值表示式,所以會取出函式obj1.foo然後通過後面的小括號進行直接呼叫(也叫間接函式引用),而後者還是隱式繫結;造成這種差異的方式主要還是在js引型解析的區別。

得到的函式被直接呼叫,那麼繫結規則是預設繫結。

**我寫js程式碼習慣書寫分號,在我們的賦值語句(obj2.bar = obj1.foo)**前面定義物件obj2的時候,如果沒有分號結尾,是會報錯的。

這種賦值語句會間接拿到事先定義的函式,並且作為這個語句的返回值。屬於特殊情況,其實實際開發,我們不可能寫這種程式碼。

ES6的箭頭函式 arrow function 箭頭函式是ES6之後新增的編寫函式的方法,寫法比函式表示式更加簡潔。

特點:

箭頭函式不會繫結this,arguments屬性; 箭頭函式不能作為建構函式來使用(說白了就是不能搭配new使用); 箭頭函式不使用上面的this的四種規則,通俗的說就是不繫結this,而是根據外層作用域來決定this的指向。

var foo = () => {console.log(this)} // 均為 window foo(); foo.call("123"); var obj = { name: "zs", foo: foo }; obj.foo();