JavaScript 你真的瞭解this指向嗎

雲崖先生發表於2020-07-31

JavaScript 你真的瞭解this指向嗎

前言

  終於開始寫this指向了,相信這對很多JavaScript的學習者來說是一個非常恐怖的環節,個人認為也算是JavaScript中最難理解的一個知識點,this非常的方便但是在你不熟悉它的情況下可能會出現很多坑。

  本篇文章將帶你充分了解this指向,用最精煉簡短的語句闡述不同情況下的this指向。

 

詳解this指向

window物件


  window是一個全域性的物件,裡面存了很多方法。

  當我們使用var進行變數命名時,變數名會存入到window物件中,以及當我們使用標準函式定義方法時函式名也會存入window物件中。

 

<script>var username = "雲崖";
​
        function show(){
                console.log("show...");
        };
​
        console.log(window.username);  // 雲崖
        window.show();  // show...
</script>

 

全域性環境


  在全域性環境中,this的指向就是window物件。

  但是我們一般不這麼用。

 

<script>var username = "雲崖";
​
        function show(){
                console.log("show...");
        };
​
        console.log(this.username);  // 雲崖
        this.show();  // show...
// 依舊可以執行,代表全域性環境下this就是window物件
        // 如果你不相信,可以列印它看看。
​
        console.log(this);  //  Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
</script>

 

普通函式


  非嚴格模式下,普通函式中this的指向為window物件。

<script>// "use strict"; // 在非嚴格模式下,普通函式中this的指向是window物件
function show() {
​
                console.log("show...");
                console.log(this);  // window
​
        };
​
        show()
​
</script>

 

  但是在嚴格模式下,普通函式中this指向為undefined

<script>"use strict"; // 在嚴格模式下,普通函式中this指向為undefined
function show() {
​
                console.log("show...");
                console.log(this);  // undefined
​
        };
​
        show()
​
</script>

 

建構函式


  當一個函式能被new時,該函式被稱之為建構函式。

  一般建構函式中包含屬性與方法,函式中的上下文指向到例項物件。

  在建構函式中的this一般指向為當前物件。對於其方法而言this指向同樣為當前物件。

 

  你可以這麼認為,在用Function定義類時,類中的方法指向當前類。

  這樣是不是好理解多了?

 

<script>"use strict"; 
​
        function User(username) {  // 在沒有class語法出現之前,這種建構函式我們通常會將它當做類來看待。
this.username = username;  // 可以稱之為類屬性
​
                console.log(this);  // User {username: "雲崖"}  代表指向當前物件
this.show = function () {  // 可以稱之為類方法
​
                        console.log(this.username);  // 雲崖
                        console.log(this);  // {username: "雲崖", show: ƒ}  代表指向當前物件
                }
​
        }
​
        let user = new User("雲崖");
        user.show();
​
</script>

 

物件字面量


  在物件中的this指向即為當前物件,同樣的在物件中的函式(方法)this指向也是當前物件本身。

  這與建構函式如出一轍。

 

<script>"use strict"; 
​
        let obj = {
                username:"雲崖",  // 最終的key都會轉為String型別,但是Symbol型別不會轉換。
​
                show:function(){ // 這裡也可以將show稱作為方法,而username即為屬性
​
                        console.log(this.username); // 雲崖
                        console.log(this);  // {username: "雲崖", show: ƒ}
​
                },
        }
​
        obj.show();
​
</script>

 

方法中的普通函式


  首先聊方法中的普通函式之前,要先知道什麼情況下的函式常被稱之為方法。

  結合本章前面介紹的內容,以下環境中的函式將被稱之為方法:

 

  在建構函式中的函式可以將其稱之為方法

  在物件中的字面量函式也可以將其稱之為方法

 

  那麼,在方法中的普通函式即是這樣的:

 

  在建構函式中的函式中的函式可以稱其為方法中的普通函式

  在物件中的字面量函式中的函式也可以將其稱為方法中的普通函式

 

  有點繞哈,看程式碼你就懂了。

  在方法中的普通函式的this指向非嚴格模式下為window物件,嚴格模式下為undefined

  值得一提的是,對於大多數開發者而言,這麼巢狀的情況很少使用。

 

<script>"use strict";
​
        function User(username) { 
​
                this.username = username; 
​
                this.show = function () {  // 方法
​
                        console.log(this.username);  // 雲崖
                        console.log(this);  // {username: "雲崖", show: ƒ}  代表指向當前物件
function inner() {  // 普通函式
                                console.log(this);  // 嚴格模式:undefined,非嚴格模式:window
                        };
​
                        inner();  // 在方法中定義一個函式並呼叫
​
                }
​
        }
​
        let user = new User("雲崖");
        user.show();
​
</script>

 

<script>"use strict"; 
​
        let obj = {
                username:"雲崖",  // 最終的key都會轉為String型別,但是Symbol型別不會轉換。
​
                show:function(){  // 方法
​
                        console.log(this.username); // 雲崖
                        console.log(this);  // {username: "雲崖", show: ƒ}
function inner(){  // 普通函式
                                console.log(this);  // 嚴格模式:undefined,非嚴格模式:window
                        };
​
                        inner();  // 在方法中定義一個函式並呼叫
​
                },
        }
​
        obj.show();
​
</script>

 

方法中的普通函式改變this指向


  那麼,怎麼改變方法中普通函式的this指向呢?

  非常簡單。使用一個常量將方法的this賦值並傳遞給其中的普通即可。

 

<script>"use strict";
​
        function User(username) { 
​
                this.username = username; 
​
                this.show = function () {  // 方法
​
                        let self = this;  // 這個this指向的當前物件,即User
function inner(self) {  // 普通函式
                                console.log(self);  // 在方法內的普通函式中使用self即可
                        };
​
                        inner(self);  // 我們將self傳遞進去
​
                }
​
        }
​
        let user = new User("雲崖");
        user.show();
​
</script>

 

<script>"use strict";
​
        let obj = {
                username: "雲崖",  // 最終的key都會轉為String型別,但是Symbol型別不會轉換。
​
                show: function () {  // 方法
​
                        let self = this;  // 這個this指向的當前物件,即obj
function inner(self) {  // 普通函式
                                console.log(self);  //  在方法內的普通函式中使用self即可
                        };
​
                        inner(self);  // 在方法中定義一個函式並呼叫
​
                },
        }
​
        obj.show();
​
</script>

 

方法中的箭頭函式


  箭頭函式這玩意兒沒有this指向,你可以理解為它始終會與外層定義自己的函式共同使用一個this,在大多數情況下是會如此,但是少部分情況會除外,比如在事件的回撥函式中,這個在下面會有舉例。

 

  在方法中的箭頭函式this指向始終會與定義自己的函式共同使用一個this

 

<script>"use strict";
​
        function User(username) {
​
                this.username = username;
​
                this.show = function () {  // 方法
​
                        console.log(this);  // 指向 User
​
                        let inner = () =>   console.log(this);  // 箭頭函式,與定義自己的外層指向同一this,即User
​
                        inner();  // 在方法中定義一個函式並呼叫
​
                }
​
        }
​
        let user = new User("雲崖");
        user.show();
​
</script>

 

事件普通函式


  事件函式是指某一動作方式後所呼叫的回撥函式,如果是普通函式那麼this指向即為事件源本身。

 

<script>"use strict";
​
        let div = document.querySelector("div");
​
        div.onclick = function (event) { 
​
                console.log(event);  // 事件
​
                console.log(this);  // div標籤,即為事件源本身,DOM物件
         }
​
</script>

 

事件箭頭函式


  事件的回撥函式如果箭頭函式,那麼this指向即為window,一句話,向上找,看在哪個環境下定義了這個箭頭函式。

  所以我們儘量不要去用箭頭函式作為事件的回撥函式。

 

<script>"use strict";
​
        let div = document.querySelector("div");
​
        // 由於是在全域性定義的,所以此時的this即為window,如果是在方法中定義的事件箭頭函式則this指向
        // 就不是window了
​
        div.onclick = event => { 
​
                console.log(event);  // 事件
​
                console.log(this);  // Window 
         }
​
</script>

 

事件箭頭函式獲取事件源


  如果想在事件箭頭函式中獲取事件源,那就不使用window了。用event引數中的一個叫target的屬性即可找到事件源。

 

<script>"use strict";
​
        let div = document.querySelector("div");
​
        div.onclick = event => { 
​
                console.log(event.target);  // 事件源DOM物件
​
                console.log(this);  // Window 
         }
​
</script>

 

改變this指向

call方法


  通過call()方法,讓原本函式的this發生改變,如下例項我們可以在user函式中使用this去給物件obj進行新增屬性。

 

  引數1:新的this指向物件

  其他引數:函式中本來的傳遞值

  特點:立即執行

 

<script>"use strict";
​
        function user(name,age) {
                this.name = name; 
                this.age = age;
                console.log(this);
        };
​
        let obj = {};
​
        user.call(obj,"雲崖",18);
​
        console.log(obj.name); // 雲崖
        console.log(obj.age); // 18
</script>

 

apply方法


  call()方法唯一不同的地方在於引數傳遞,其他都一樣。

 

  引數1:新的this指向物件

  引數2:函式中本來的傳遞值,請使用陣列進行傳遞。

  特點:立即執行

 

<script>"use strict";
​
        function user(name,age) {
                this.name = name; 
                this.age = age;
                console.log(this);
        };
​
        let obj = {};
​
        user.apply(obj,["雲崖",18]);
​
        console.log(obj.name); // 雲崖
        console.log(obj.age); // 18
</script>

 

bind方法


  該方法最大的特點是具有複製特性而非立即執行,對於函式的引數傳遞可以有多種。

  但是我這裡只介紹一種。

 

  引數傳遞:使用bind()方法時傳遞一個this指向即可

  特性:會返回一個新的函式。

 

<script>"use strict";
​
        function user(name,age) {
                this.name = name; 
                this.age = age;
                console.log(this);
        };
​
        let obj = {};
​
        // 可以這麼理解,使用bind()方法的第一步,告訴this指向誰,第二步,複製出新函式,第三步,新函式的this已經改變。其他地方與原函式相同。
        let new_func = user.bind(obj); // obj傳遞進去,返回一個新函式,該新函式與user函式除了this指向不一樣其他均相同。
        new_func("雲崖",18)
​
        console.log(obj.name); // 雲崖
        console.log(obj.age); // 18
</script>

 

總結

 

 window物件當前物件上級this指向事件源DOM物件undefined
全域性環境(不在函式中)        
普通函式 非嚴格模式:√       嚴格模式:√
建構函式        
物件        
建構函式中的方法        
物件中的字面量方法        
建構函式中的方法中的普通函式 非嚴格模式:√       嚴格模式:√
物件中的字面量方法中的普通函式 非嚴格模式:√       嚴格模式:√
建構函式中的方法中的箭頭函式      
物件中的字面量方法中的箭頭函式      
事件普通函式        
事件箭頭函式 大概率是√,主要看上層的this指向      

 

後言

  本人也是一名JavaScript的初學者,很多地方可能闡述不到位描述不清楚歡迎留言。

  最後我想吐槽的是,怎麼啥都是物件啊!!很難區分啊!!實在不適應將{k1:v1,k2:v2}的這種叫物件,真的太不習慣了!!!

 

相關文章