前言
最近在看回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的指向是當前的上下文。
我是剛畢業一年多的小菜鳥,上述為個人學習整理內容,水平有限,如有錯誤之處,望各位園友不吝賜教!如果覺得不錯,請點選推薦和關注!謝謝~๑•́₃•̀๑ [鮮花][鮮花][鮮花]