【JavaScript】聊聊js中關於this的指向

我恨bug發表於2024-07-03

前言

最近在看回JavaScript的面試題,this 指向問題是入坑前端必須瞭解的知識點,現在迎來了ES6+的時代,因為箭頭函式的出現,所以感覺有必要對 this 問題梳理一下,所以剛好總結一下JavaScript中this指向的問題。

什麼是JavaScript

在瞭解this指向的問題前,首先得了解一下什麼是JavaScript。

JavaScript(簡稱“JS”)是一種具有函式優先的輕量級,解釋型或即時編譯型的程式語言。JavaScript基於原型程式設計、多正規化的動態指令碼語言,並且支援物件導向、命令式、宣告式、函數語言程式設計正規化、支援函數語言程式設計、閉包、基於原型的繼承等高階功能。

什麼是this

面嚮物件語言中 this 表示當前物件的一個引用。

但在 JavaScript 中 this 不是固定不變的,它會隨著執行環境的改變而改變。

在方法中,this 表示該方法所屬的物件。

如果單獨使用,this 表示全域性物件。

在函式中,this 表示全域性物件。

在函式中,在嚴格模式下,this 是未定義的(undefined)。

在事件中,this 表示接收事件的元素。

類似 call() 和 apply() 方法可以改變 this 的指向 ,引用到任何物件。

所以this的指向完全取決於函式的呼叫方式。

this的指向

接下來我將在(非嚴格模式下)透過下面例圖與例子來了解this的指向。

1.不使用new關鍵字,使用dot呼叫。

var obj = {
    name: 'bug',
    obj2: {
           name: 'bug2',
           fn: function () {
                console.log(this.name); //bug2
           }
     }
}
//此處透過obj.obj2.fn(),呼叫了obj中的obj2中的fn函式,此時fn函式中this的指向為dot (.) 前面的物件,即為obj2,obj2中的name即是bug2。
obj.obj2.fn();

2.使用new關鍵字呼叫。

function fn() {
  this.x = 1;
}

//此處透過new關鍵字生成了一個例項物件,此時的this指向了該例項物件fn
var obj = new fn();

//此時的obj的結構為{x:1},所以obj.x=1
obj.x // 1

講到new關鍵字就剛好衍生出看另外一個關鍵點,如果我用new去建立一個例項物件,這個時候例項物件有返回值呢?

通常情況下是不應該有顯式的返回值的。

但是如果當return返回的是一個物件,那麼將返回該物件。

但是如果當return返回非物件型別(比如數字、字串等),那麼就不會影響到new關鍵字對物件的建立。

以下就用幾個例子來驗證一下:

①return 空物件

function fn() 
{ 
   this.name= 'bug'; 
   //此處return回了一個物件
   return {}; 
}
var obj = new fn(); 
//此時因為return回的是一個物件,所以此時的obj的結構是返回的空物件{},所以obj.name才會是undefined
console.log(obj.name); //undefined

②return一個非空物件

function fn() 
{ 
   this.name= 'bug'; 
   //此處return回了一個物件
   return {name:'bug2'}; 
}
var obj = new fn(); 
//此時因為return回的是一個非空物件,所以此時的obj的結構是返回的非空物件{name:'bug2'},所以obj.name是bug2
console.log(obj.name); //bug2

③返回數字

function fn() 
{ 
   this.name= 'bug'; 
   //此處return回了一個數字
   return 11; 
}
var obj = new fn(); 
//此時因為return回的是一個數字,所以此時返回的例項物件不受影響,結構是{name:'bug'},所以obj.name是bug
console.log(obj.name); //bug

④返回字串

function fn() 
{ 
   this.name= 'bug'; 
   //此處return回了一個字串
   return 'xxxxx'; 
}
var obj = new fn(); 
//此時因為return回的是一個字串,所以此時返回的例項物件不受影響,結構是{name:'bug'},所以obj.name是bug
console.log(obj.name); //bug

既然現在進入了Es6+的時代了,就不得不講一講箭頭函式的this指向了

1.什麼是箭頭函式

箭頭函式是ECMAScript 6中新增的一種函式定義方式,也被稱為Lambda函式。 它使用箭頭(=>)符號來替代傳統的function關鍵字,從而更簡潔地定義函式,使程式碼更加簡潔易讀。

箭頭函式有以下特點:

①語法簡潔:箭頭函式表示式的語法比普通函式更簡潔,使用箭頭(=>)符號來定義函式,可以省略一些不必要的語法元素,如function關鍵字、大括號和引數列表周圍的括號(如果只有一個引數)。

②this繫結:箭頭函式不繫結自己的this,它會捕獲定義時所在上下文的this值,這使得在回撥函式或巢狀函式中使用箭頭函式時,this的指向更加明確和可預測。

③沒有arguments物件:箭頭函式沒有自己的arguments物件,這意味著它們無法訪問到傳統函式的特殊屬性arguments。

④不能用作構造器:箭頭函式不能作為構造器使用,即它們不能用作類的例項化。

2.箭頭函式的this指向

因為箭頭函式不繫結自己的this,它會捕獲定義時所在上下文的this值。所以簡單的說就是箭頭函式沒有屬於自己的this。

一下用個例子來簡單瞭解。

①正常function函式

const obj={
      mythis: function(){
           console.log(this) //指向了上一級物件obj
      }
}
obj.mythis() //返回了obj物件

②箭頭函式

const obj={
     mythis: ()=>{
          console.log(this) //因為箭頭函式沒有自己的this,所以指向的是window
     }
}
obj.mythis() //返回了window

哦這裡還有一個坑,就是前面說的,this指向完全取決於函式的呼叫方式。

你再看看這道題最終返回的是什麼?

const obj={
    mythis: function(){
       console.log(this)
    }
}
var a =obj.mythis
a()
點選檢視答案與解析
//是不是有小夥伴認為這裡使用的是function,所以返回的還是mythis的上一級物件obj ???
//不不不,這時候返回的是window!因為this指向完全取決於函式的呼叫方式
//上述例子①為何返回的是obj是因為它是直接obj.mythis()去呼叫,this指向是mythis的上一級物件
//但是本例子是透過減mythis直接賦值給a,此時,a 成為一個普通的函式引用,它只是 obj.mythis 的一個複製,並沒有 obj 物件的上下文資訊
//所以,當 a 作為一個普通函式呼叫時(不作為物件的方法呼叫),在非嚴格模式下,JavaScript 中的 this 預設指向全域性物件 window
const obj={
    mythis: function(){
       console.log(this)
    }
}
var a =obj.mythis
a() //window

當然,this的指向除了呼叫的方式不同而不同的同時,也可以透過其它方式強制改變this的指向!那就是使用call、apply、bind。

什麼是call、apply、bind,區別是什麼?

1.什麼是call?

call方法可以接受兩個引數,第一個引數就是this的指向,指向xxx,第二個引數為一個引數列表。當第一個引數為null或者undefined時,this預設指向window。

function fn(...args) {
    console.log(this, args);
}
let obj = {
    name: "bug"
}

//將fn的this指向obj,並傳入引數列表 1,2
fn.call(obj, 1, 2); //{name:'bug'} , [1,2]

//次數fn中的this指向為window
fn(1, 2) //window , [1,2]

//當第一個引數為null時,this指向為window
fn.call(null,[1,2]);//window , [1,2]

//當第一個引數為undefined時,this指向為window
fn.call(undefined,[1,2]);//window , [1,2]

2.什麼是apply?

apply方法可以接受兩個引數,第一個引數就是this的指向,指向xxx,第二個引數為一個引數陣列。當第一個引數為null或者undefined時,this預設指向window。

function fn(...args) {
    console.log(this, args);
}
let obj = {
    name: "bug"
}

//將fn的this指向obj,並傳入引數陣列 [[1,2]]
fn.apply(obj, [1,2]); //{name:'bug'} , [[1,2]]

//次數fn中的this指向為window
fn([1,2]) //window , [[1,2]]

//當第一個引數為null時,this指向為window
fn.apply(null,[1,2]);//window ,  [[1,2]]

//當第一個引數為undefined時,this指向為window
fn.apply(undefined,[1,2]);//window , [[1,2]]

3.什麼是bind?

bind方法跟call、apply十分相似,第一個引數也是this的指向,第二個引數傳的也是一個引數列表,但是!這個引數列表可以分多次傳入!並且改變完this的指向並不會立刻執行,而是返回一個已經永久改變this指向的函式

function fn(...args) {
     console.log(this, args);
}
let obj = {
     name: "bug"
}
const bindFn = fn.bind(obj); //this變為obj,且不會立馬執行
bindFn(1, 2) //得透過呼叫才會執行,並傳入引數列表1,2,最終this指向obj {name:'bug'}
fn(1, 2) //this執行window

4.call、apply、bind的區別是什麼?

①三者都可以改變函式的 this 物件指向

②三者第一個引數都是 this 要指向的物件,如果如果沒有這個引數或引數為 undefined 或 null,則預設指向全域性 window

③三者都可以傳參,但是 apply 是陣列,而 call 是引數列表,且 apply 和 call 是一次性傳入引數,而 bind 可以分為多次傳入bind 是返回繫結this之後的函式,apply、call 則是立即執行

總結

簡單來說,this的指向不是固定不變的,它會隨著執行環境的改變而改變,具體怎麼改變完全取決於函式的呼叫方式。

箭頭函式沒有屬於自己的this,作為方法的箭頭函式this的指向是當前的上下文。

我是剛畢業一年多的小菜鳥,上述為個人學習整理內容,水平有限,如有錯誤之處,望各位園友不吝賜教!如果覺得不錯,請點選推薦和關注!謝謝~๑•́₃•̀๑ [鮮花][鮮花][鮮花]

相關文章