JavaScript進階(六):this 變化的原理,爲什麼 js 裏面 this 這麼亂,指定 this

this 這個東西在 js 裏面特別的亂,我們得知道幾件事,就是 this 這個東西,到底是什麼東西,它是幹什麼的,怎麼回事等等。

這裏我們可以用一句話來描述,this 它取決於一個東西,就是“誰在調用”。

首先,this 這個東西它是用在哪的呢?

它是用於函數的內部,在函數之外的地方你去用 this 是沒有意義的。(函數內部也就是方法內部)

所以這個 this 它其實就取決於,我當前的這個函數,或者這個方法,是誰在調用,誰調用我,this 就是誰,就這麼簡單。

我們先來看一個例子,具體看看這個 this 到底怎麼回事。

這裏我們有一個函數,名字叫做 show,在它裏面我們想要把這個 this 給打印出來:

注意一件事,在其他的語言裏面,比如 java,this 是固定的。什麼意思呢?

就是說我這個函數,或者說代碼一旦寫好了,這個 this 就是固定的,就是指某一個東西,某一個對象,它是固定的。

而在 js 裏面不一樣,js 裏的 this 之所以亂,是因爲它這個 this 是能變的。

那麼我們來試一下,比如說現在,我就來執行以下這個 show:

你可以看到,在正常的情況下,什麼都沒做的情況下,這個 this 它是一個 window。

當然這時候大家可能奇怪說,爲什麼是 window?

這個其實跟 js 自身的特性有關係,就是說,全局的東西,包括函數,它其實都是屬於 window 的,這是 js 的一個特點。

比如說平常我們寫了一個 alert('123'),這個 alert 其實就是一個全局的函數。

其實它的全稱叫做 window.alert。

你可以看到,兩種寫法的效果其實是一樣的。

js 這個語言的特點就是,全局的東西都屬於 window。

那麼所以在這種情況下,我這麼去寫:show()

其實完全等價於:window.show()

所以這時候,我們就知道爲什麼它的 this 是 window 了,因爲是 window 在調用它。

show() 我這麼寫其實只是一個簡寫,把 window 省了而已。

但是我們在上面說過,this 是會變的。

比如說現在這個 show 函數,它本身不變,但隨着我們用法的不同,this 可能就不一樣。

'use strict' 這個叫做嚴格模式,什麼意思呢?

js 這個語言它其實有很多語法層面的漏洞或者弱點之類的東西,而這個嚴格模式,它可以幫助我們 js 運行在一個更加嚴格的狀態之下,這樣的話,很多問題其實可以得到解決。

那麼現在你可以看到,this 就變成 undefined 了。

 

大家可能會覺得很奇怪,你剛不是都說過全局東西都屬於 window 嗎,那現在怎麼說?

明確的告訴大家,很簡單的一件事:

首先,一切全局的東西都屬於 window 這個事本身嚴謹嗎?其實它本身就不嚴謹。

爲什麼這麼說呢,比方說,現在我們是 js,一切都屬於 window,一點問題都沒有。

但是,js 早就已經不是隻能工作在瀏覽器的狀態之下了,js 有沒有可能工作在後臺,比方說 node.js?

 

那麼實際上來說,這時候就有一個問題了,後臺的 js 根本就沒有 window 這個概念。

所以在這種情況下,也就是嚴格模式下,這個全局的 show 函數,它並沒有一個真正意義上的屬於誰的概念。

它就是一個函數,全局的,它不屬於任何人。

所以這時候它是一個 undefined 就可以理解了。

 

所以 this 第一個特別混亂的地方就是,js 的 this 是動態的,說白了,就是它不是死的,它可以變,根據你使用的方法不同,它是可以變化的。

比如,現在我是 undefined,如果我現在補齊了,寫上 window.show()

你會發現,它的 this 就變成 window 了。

所以這個時候,加不加 window 其實還是有區別的。

 

那麼還有沒有其他的呢,我們再來換一個不是全局的。

比如,我們有個數組 arr,現在想要把這個 show 給加到 arr 的身上,然後我們在 arr 的身上再來執行這個 show:

可以看到,當我們把 show 加到 arr 的身上後,你會發現這個 this 它就又不一樣了。

這個 this 它就變成 arr 了。

說白了,我們這個方法是固定的,誰調用我,那 this 就是誰。

現在是 arr 在調我,所以現在這時候 this 就是 arr。

當然,這個事裏面,嚴格模式是沒有影響的。

嚴格模式它影響的只是那個全局的東西,其他的東西它都不管,所以我把它註釋掉,this 依然是 arr:

所以,當我把它加到一個對象上,變成一個對象的方法之後,它的 this 也跟着過去了。

所以說它是取決於你調用的方法。

 

然後還有一種比較常見的。

如果我們給 document 加個點擊事件,並且把 show 加到上面:

這個時候可以看到,當我們點擊頁面的時候,你就可以發現這個 this 它就變成了 document。

所以當我們去用到事件的時候,其實它的 this 也會受到影響。

這個 this,它就變成了你當前的那個事件,屬於的哪一個對象。

比如這個事件 onclick 是 document 身上的,那這個 this 就是 document。

 

以及還有一個東西,如果我們用定時器來跑這個東西:

然後這個時候,你就會發現 this 它又變成window了。

而且你會發現,下面我們是加着嚴格模式的情況下,它也還是 window。

其實這個也很好解釋,我們的這個定時器,它是由誰來觸發的?

其實定時器是由瀏覽器來觸發的。

而瀏覽器,其實也就是 window 間接的來執行了這個東西。

說白了,定時器它其實也是屬於 window 的,和上面 alert 的例子一樣,這裏其實也是一個簡寫:

所以你會發現 this 受很多東西的影響,隨着你使用方式的不同,它會變來變去的。

 

那麼 js 的作者他當初爲什麼要這樣設計 this?

我們想要理解任何一個東西,最好的辦法是你需要先知道它爲什麼而出現,爲什麼會有這個東西。

其實挺簡單的,比方說,假設我們處於一個沒有 this 的世界,那麼這時候我們寫代碼的時候怎麼寫?

比方說這有一個最簡單的類叫 Person,然後它裏面有個方法 fn,這個方法就是想要來彈出我的 name:

class Person {

    fn(){

        alert( ???.name )

    }

}

那這個時候,你怎麼寫,你寫什麼點 name?

假設我現在就想彈 name,比方我現在 new 一個實例:

class Person {

    fn(){

        alert( p.name )

    }

}

let p = new Person('大木')

那這個時候,上面是不是就可以改成 p.name 了。

理論上來說,這麼寫是能出來的,但是有一個問題:你能保證用你這個類的人,一定給實例取名叫 p 嗎?

如果別人叫 p2,那你上面是不是也得跟着改?那這個類它本身的寫法取決於人家怎麼去用這個類,這個就太詭異了。

所以說白了,this 的設計,本身是非常的合理的,它的目的,就是爲了要來幫助我們使用當前的那個實例。

沒有 this,你會發現這個事就做不了,而如果用了 this,是不是就會很方便:

class Person {

    fn(){

        alert( this.name )

    }

}

let p = new Person('大木')

所以說 this 本身是很有價值的,它是爲了幫助我們來使用當前的那個實例。

 

但是接下來的另一個問題就是,js 的 this 爲啥這麼亂?

相信很多人,包括我在內,都想問 js 作者這個問題

說出來可能你不信,js 作者當初之所以把 this 搞那麼亂,他是爲了能夠讓 js 更簡單易用。

大家可能會覺得奇怪,我這沒看出來哪簡單,哪易用了。

 

首先我們爲了理解這個事,我們需要先來看一看其他的語言。

首先,幾乎在所有的語言裏面,函數這個東西,是不能單獨存在的。

比方說 java,你可以問下週圍 java 的同事:java 裏面我想就單寫一個函數,不寫類,就只是函數,行嗎?

人家多半會說:哥們,還沒睡醒吧。

實際上來說,在其他的語言裏面,函數是不能獨立存在的,就沒這個說法,它必須得屬於某一個類。

所以在這種情況下,它那個 this 是不亂的,this 就一個指向,就是當前對象。

但是在我們的 js 裏面,你會發現函數這個東西,可以存在在各種各樣的地方。

比方說,我可不可以給事件加個函數?定時器裏面可不可以有函數?某個類裏面可不可以搞個函數?而且這個函數我還可以賦值,賦來賦去,一會給你,一會給他。

實際上來說,我們 js 裏面的 this 爲什麼亂,就是因爲這個函數本身它能到處竄。

如果這個函數本身它是固定的,就不能動,那這時候 this 其實會變的非常的簡單。

所以歸根到底,我們 js 裏面的 this 之所以亂,是因爲這個函數特別的靈活,可以獨立存在,也可以給別人,就是因爲這個原因。

這也是爲什麼 js 作者當初要搞這麼亂,就是爲了簡單易用。

 

所以我們來簡單的總結一句話,就是:這個函數,你甩給誰,this 就變成誰。

如果我把這個函數,甩到一個對象上面做方法:arr.show = show。那麼這個時候,this 就變成了 arr。

而反過來,如果我把這個函數甩到一個地方去給別人當事件:document.onclick = show。那麼實際上來說,它就變成了當前的那個事件元素。

這兩種其實本質上是一樣的:

arr.show = show

document.onclick = show

你可以發現,從寫法角度就完全是一樣的。

所以說,事件本質上其實是一個方法,就是當某一個事發生的時候,比如用戶點擊的時候,它就會調用自己的 onclick 方法。

並且,在 js 裏面,如果用定時器的話,this 會變成 window,究其原因,是因爲定時器它是屬於 window 的。

 

那麼接下來,我們稍微回顧下剛纔說了點啥:

1,this 變來變去的核心。

你就看一件事,現在你的這個函數,是由誰在調用,誰調用,this 就是誰。

所以你在看 this 到底是誰的時候,不要去到處亂瞟,你就看看誰在調它,就行了。

2,this 亂的原因。

this 它之所有亂,並不是別的原因,而是因爲我們 js 本身是一個很靈活的語言,所以會導致這個函數竄來竄去,所以纔會導致這個問題。

 

當然 this 是一個實用性很強的東西,我們在用的過程當中,會逐漸的體會到它各種各樣的用法。

在接下來,我們要說一個很重要的東西,就是我希望能夠去操作這個 this,或者說我希望能夠讓這個 this 變成我想要的那個東西。

我們看到了這個 this 變來變去,那麼我們就想知道,怎麼去操作這個 this。

一般來講,有兩種方法。

第一種,用一些函數的方法。

什麼意思呢?

首先,我們平常寫的 function,這個東西,它也是一個對象。

function show() { }

其實函數也是一個對象,js 裏面的一切都是對象,函數也不例外,所以它也有方法。

比如,爲了證明這點,我們稍微的來看一下。

首先,我們想要來看看這個 show 有沒有 constructor:

這時候你可以看到一個 Function,注意,是大寫的 F。

所以實際上來說,所有的函數,它都是 Function 這個類的一個實例。

或者說的更直白一點,它完全等價於這麼寫:

你平常那麼寫,其實只是一個簡寫,爲了讓你方便而已。

所以,我們現在就明確了一個事,函數,它也是一個對象。

既然函數是個對象, 所以它也有自己的方法。

那麼在這一共有三種常見的方法,對我們操作 this 是非常有用的:call、apply、bind。

這裏就不說三個函數的用法和原理了,大家有興趣的可以看下我前面的博客, js 基礎裏面都是有寫的。

 

第二種,我們也可以用箭頭函數。

箭頭函數是 ES6 裏面推出的一個特性,它既可以幫助我們把函數做一個簡寫簡化。

同時,其實它也可以幫我們來修正這個 this 的問題。

箭頭函數它的特點很簡單, 箭頭函數內部的 this,它永遠跟箭頭函數的外部一致。

說白了,用人話說就是,你這個箭頭函數寫在哪,那個地方的 this,就作爲它裏面的 this 來用,相當於它會自動的去 bind 一下,不需要你去管。

比方說:

這 2 個 this 是不一樣的。

上面的 this 指的是當前實例,就是 new 出來的那個 a。

而下面的 this,它是 document。

所以你會發現,當我點擊頁面的時候,是 undefined。

 

然後這時候你有兩種解決方法:

第一種方法,我們可以給它 bind 一下。

可以看到,這時候就出來了。

當然這種寫法可以起作用,但略微有一點麻煩。

所以第二種方法,我們可以改成箭頭函數。

你會發現,效果上是一樣的。

所以說這個箭頭函數其實相當於自帶一個 bind,並且它 bind 的那個值,就是你當前所處的那個 this。

所以,這兩種寫法,理論上是一個東西,它內部是一致的,就這麼簡單。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章