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}
的這種叫物件,真的太不習慣了!!!