前言
終於開始寫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指向 | √ |