JavaScript中的this指向綁定規(guī)則及常見面試總結(jié)
一、引言
this可以說是前端開發(fā)中比較常見的一個關(guān)鍵字,由于其指向是在運行時才確定,所以大家在開發(fā)中判斷其方向時也會很模糊,今天就把this的指向問題拆開了,揉碎了,好好講一講。
先來看一個場景,看看該處的 this 應(yīng)該指向哪:首先在 request.js
中定義一個 getAction
函數(shù)
export function getAction(url,parameter) { let headers = {} if (this && this.realReferer && this.realReferer !== '') { headers.realReferer = window.location.origin + this.realReferer } return axios({ url: url, method: 'get', params: parameter, headers }); }
然后在 test.vue
文件中引用該 getAction
函數(shù)并使用
import { getAction } from '@api/request' export default { methods: { getTableData() { getAction(this.url.requestUrl).then(res => { //1.這個時候getAction中的this將打印出什么 //2.在該處打印this,會輸出什么 console.log(this); }) }, } }
現(xiàn)在有兩個問題:
- 在
test.vue
中調(diào)用getAction()
時,此時其內(nèi)部,也就是request.js
中的 this 指向什么? - 在
getAction()
then 后的箭頭函數(shù)中的 this 指向什么?
思考一下能判斷出這兩個this的指向嗎?先賣個管子,等咱們再講完this的相關(guān)原理后再來解答這兩個問題。這篇文章會從這幾個方面講解:
- 什么是this,this和執(zhí)行上下文的關(guān)系
- this中的默認、隱式、顯式和new的綁定規(guī)則
箭頭函數(shù)中的this指向問題
二、什么是this?
this 其實就是一個JavaScript語言中的一個關(guān)鍵字, 它的值是某個對象引用值,其指代的是當前執(zhí)行上下文中的對象。那么為何需要this?我們先來看看一個例子:
var testObj = { name: "testObj", print: function () { console.log(name) } } var name = "global name"; //想通過調(diào)用print()來調(diào)用testObj中的name testObj.print();//global name
從結(jié)果可知,最后print()
輸出"global name", 而不是 testObj
中的 name。為何出現(xiàn)這種情況?
這是因為 JavaScript 語言的作用域鏈是由詞法作用域決定的,而詞法作用域是由代碼結(jié)構(gòu)來確定的:
- 當
testObj.print()
執(zhí)行時,這段代碼的詞法作用域是全局作用域,所以這個時候 js 引擎會去全局作用域中尋找 name,最后打印出“global name”。
因此為了避免這種情況,JavaScript 設(shè)計者引入了 this 機制,來調(diào)用對象的內(nèi)部屬性,如下代碼:
var testObj = { name: "testObj", print: function () { console.log(this.name) } } var name = "global name"; testObj.print();//testObj
最后就能夠通過testObj.print()
來調(diào)用對象內(nèi)部的屬性了。
不同于詞法作用域鏈,this的指向是在運行時才能確定,實際上當執(zhí)行上下文創(chuàng)建后,會生成一個this引用值,指向當前執(zhí)行上下文對象,如下圖所示:
而 js 引擎在執(zhí)行代碼時的運行時上下文主要有三種:全局執(zhí)行上下文、函數(shù)執(zhí)行上下文和 eval 執(zhí)行上下文。不同場景的this指向如下:
//全局執(zhí)行上下文,當前對象是window console.log(this);//window //函數(shù)執(zhí)行上下文外部對象是全局對象,所以指向全局對象window function test(){ console.log(this);//window } //函數(shù)執(zhí)行上下文外部對象是test,因此指向當前的對象test var test = { test: function(){ console.log(this);//test{...}對象 } } //eval執(zhí)行上下文,根據(jù)默認綁定規(guī)則,指向全局對象window eval(`console.log(this); `) //window
正是因為this在運行中才得以確定其指向的上下文對象,所以為了規(guī)范和標準化this的指向方式,規(guī)定了一系列的綁定規(guī)則,來決定什么情況下this會綁定到哪個對象。下面就來聊聊this的綁定規(guī)則
三、this 綁定規(guī)則
this的綁定大致可以劃分為默認、隱式、顯式和new四種場景下的綁定規(guī)則:
1. 默認綁定
當函數(shù)被獨立調(diào)用時,會將this綁定到全局對象。瀏覽器環(huán)境下是window, 嚴格模式是undefined主要有以下幾種場景:
//1. 定義在全局對象下的函數(shù)被獨立調(diào)用 function test(){ console.log("global:", this); } test();//window //2. 定義在某個對象下的函數(shù)被獨立調(diào)用 var testObj = { test: function(){ console.log("testObj:", this); } } var testfun = testObj.test; testfun();//window //3. 定義在某個函數(shù)下的函數(shù)被獨立調(diào)用 function testFun(fn){ fn(); } testFun(testObj.test); //window
2. 隱式綁定
當函數(shù)作為對象的方法被調(diào)用時,隱式綁定規(guī)則會將this綁定到調(diào)用該方法的對象,也就是"誰調(diào)用,就指向誰"。
const obj = { name: 'innerObj', fn:function(){ return this.name; } } //調(diào)用者是obj, this指向obj console.log(obj.fn());//innerObj const obj2 = { name: 'innerObj2', fn: function() { return obj.fn(); //此時是obj調(diào)用fn,所以此時this指向obj } } //調(diào)用者是obj, this指向obj console.log(obj2.fn())//innerObj
現(xiàn)在我們可以回答引言中的問題1:在request.js
的getAction()
中this指向test.vue
中的全局vue對象,因為import {getAction} from '@api/request'
后,相當于vue對象調(diào)用了getAction()
,因此其內(nèi)部的this方向符合隱式綁定規(guī)則,所以指向調(diào)用者——test.vue
中的全局vue對象
3. 顯式綁定
顯式綁定主要指通過call、apply和bind方法可以顯式地綁定this的值:
call 方法
語法: function.call(thisArg, arg1, arg2, ...)
:
參數(shù): thisArg
表示 this 指向的上下文對象, arg1...argn
表示一系列參數(shù)
功能: 無返回值立即調(diào)用 function 函數(shù)
var test = { } function test2(){ console.log(this); } //此時是獨立函數(shù),因此指向全局對象 test2();//window //call顯式綁定,將函數(shù)內(nèi)部的this綁定至call中指定的引用對象 test2.call(test);//test
apply 方法
語法: function.apply(thisArg, [argsArray])
:
參數(shù): thisArg
表示 this 指向的上下文對象, argsArray
表示參數(shù)數(shù)組
功能: 沒有返回值, 立即調(diào)用函數(shù)
apply 和 call 的區(qū)別在于傳參,call 傳的是一系列參數(shù),apply 傳的是參數(shù)數(shù)組
var test = { } function test2(name){ console.log(this); console.log(name); } //此時是獨立函數(shù),因此指向全局對象 test2();//window //call顯式綁定,將函數(shù)內(nèi)部的this綁定至call中指定的引用對象 test2.apply(test, ["name"]);//test, name test2.call(test, "name"); //test
bind 方法
語法:function.bind(thisArg[, arg1[, arg2[, ...]]])
參數(shù): thisArg
表示 this 指向的上下文對象; arg1, arg2, ...
表示 要傳遞給函數(shù)的參數(shù)。這些參數(shù)將按照順序傳遞給函數(shù),并在調(diào)用函數(shù)時作為函數(shù)參數(shù)使用
功能: 返回原函數(shù) function 的拷貝, 這個拷貝的 this 指向 thisArg
var test = { fun: function(){ console.log(this); var test = function(){ console.log("test", this); } //1. 因為test.fun()在全局作用域中,所以屬于獨立函數(shù)調(diào)用,默認綁定規(guī)則指向全局對象 test(); //window //2. bind方法會創(chuàng)建一個原函數(shù)的拷貝,并將拷貝中的this指向bind參數(shù)中的上下文對象 var test2 = test.bind(this); test2();//test //3. apply方法會將this指向參數(shù)中的上下文,并立即執(zhí)行函數(shù) test.apply(this);//test } } test.fun();
4. new 綁定
主要是在使用構(gòu)造函數(shù)創(chuàng)建對象時,new 綁定規(guī)則會將 this 綁定到新創(chuàng)建的實例對象,因此構(gòu)造函數(shù)中用 this 指向的屬性值和參數(shù)也會被賦給實例對象:
function funtest(){ this.name = "funtest" } var tete = new funtest(); console.log(tete.name); //"funtest"
new 操作符實際上的操作步驟:
- 創(chuàng)建一個新的對象 {}
- 將構(gòu)造函數(shù)中的 this 指向這個新創(chuàng)建的對象
- 為這個新對象添加屬性、方法等
- 返回這個新對象
等價于如下代碼:
var obj = {} obj._proto_ = funtest.prototype funtest.call(obj)
5. 綁定規(guī)則的優(yōu)先級
上述的綁定規(guī)則有時會一起出現(xiàn),因此需要判斷不同規(guī)則之間的優(yōu)先級,然后再來確定其 this 指向:
a. 首先是默認綁定和隱式綁定,執(zhí)行以下代碼:
function testFun(){ console.log(this); } var testobj = { name:"testobj", fun:testFun } //若輸出window,則證明優(yōu)先級默認綁定大于隱式綁定; //若輸出testobj,則證明優(yōu)先級隱式綁定大于默認綁定; testobj.fun()//testobj
輸出為 testobj 對象,所以隱式綁定的優(yōu)先級高于默認綁定b. 下面來看一下隱式綁定和顯式綁定,執(zhí)行以下代碼:
function testFun(){ console.log(this); } var testobj = { name:"testobj", fun:testFun } //若輸出testobj,則證明優(yōu)先級隱式綁定大于顯式綁定 //若輸出{}, 則證明優(yōu)先級顯式綁定大于隱式綁定 testobj.fun.call({})//{}
結(jié)果輸出 { }
,說明顯式綁定優(yōu)先級大于隱式綁定c. 顯式綁定的 call, apply,bind 的優(yōu)先級相同,與先后順序有關(guān),看以下代碼:
function testFun(){ console.log(this); } var testobj = { name:"testobj", fun:testFun } //若輸出testobj,則證明優(yōu)先級隱式綁定大于顯式綁定 //若輸出{}, 則證明優(yōu)先級顯式綁定大于隱式綁定 testobj.fun.call({})//{} testobj.fun.call(testobj)
d. 最后來看看顯式綁定和 new 綁定的優(yōu)先級,執(zhí)行以下代碼:
function testFun(){ console.log(this.name); } var testobj = { name:"testobj", } testFun.call(testobj);//testobj //new 操作符創(chuàng)建了一個新的對象,并將this重新指向新對象 //覆蓋了testFun原來綁定的testobj對象 var instance = new testFun(); console.log(instance.name) //undefined
從結(jié)果可知,new 綁定的優(yōu)先級大于顯式綁定
最后總結(jié)一下 this 綁定的 優(yōu)先級是:
fn()(全局環(huán)境)(默認綁定)< obj.fn()(隱式綁定) < fn.call(obj)=fn.apply(obj) = fn.bind(obj)(顯式綁定)< new fn()
6. 綁定的丟失
有時 this 綁定可能會在某些情況下丟失,導(dǎo)致 this 值的指向變得不確定:
賦值給變量后調(diào)用
當使用一個變量作為函數(shù)的引用值,并使用變量名執(zhí)行函數(shù)時,會發(fā)生綁定丟失,此時 this 會默認綁定到全局對象或變成 undefined(嚴格模式下)
var lostObj = { name: "lostObj", fun: function(){ console.log(this); } } var lostfun = lostObj.fun; lostfun();//window lostObj.fun();//lostObj
從結(jié)果發(fā)現(xiàn),lostfun
雖然指向?qū)ο笾械姆椒?,但是在調(diào)用時發(fā)生了 this 綁定丟失。因為當賦值給變量時,對象中的 fun
就失去了與對象的關(guān)聯(lián),變成了一個獨立函數(shù),所以此時執(zhí)行 lostfun
也就相當于執(zhí)行獨立函數(shù),默認綁定到全局對象。
那如果通過對象來執(zhí)行呢?看如下代碼:
var lostObj = { name: "lostObj", fun: function(){ console.log(this); } } var lostObj2 = { name: "lostObj2", fun: lostObj.fun } var lostfun = lostObj.fun; lostfun();//window lostObj.fun();//lostObj lostObj2.fun();//lostObj2
同樣,一旦將方法賦值給變量后,其內(nèi)部與對象的關(guān)聯(lián)就此丟失,默認綁定到全局對象。但是將變量放到對象中后,就與該對象進行關(guān)聯(lián)。所以該方法執(zhí)行后的 this 執(zhí)行了 lostObj2
對象。
函數(shù)作為參數(shù)傳遞
將函數(shù)作為參數(shù)傳遞到新函數(shù)中,并在新函數(shù)中執(zhí)行該參數(shù)函數(shù):
var lostObj3 = { name: "lostObj3", fun: function(){ console.log(this.name); } } var name = "global" function doFun(fn){ fn(); } doFun(lostObj3.fun);//global
從結(jié)果可知,當函數(shù)作為參數(shù)傳遞后,其形參 fn 被賦值為 lostObj3.fun
。實際上也相當于賦值給變量后調(diào)用這種情況,而且 doFun()
作為獨立函數(shù)調(diào)用,所以其 this 也就指向全局對象了
回調(diào)函數(shù)
如果將對象方法作為回調(diào)函數(shù)傳遞給其他函數(shù),this 綁定也可能丟失
var lostObj4 = { name: 'lostObj4', fun: function() { setTimeout(function() { console.log(`Hello, ${this.name}!`); }); } }; lostObj4.fun(); // Hello, undefined!
因為 setTimeout
的回調(diào)函數(shù)最后會以普通函數(shù)的形式調(diào)用,所以其 this 指向的是全局對象,所以即便是 lostObj4
調(diào)用 fun()
,最后其內(nèi)部的 this 仍然會丟失。
嵌套函數(shù)
當某個函數(shù)是嵌套在另一個函數(shù)內(nèi)部的函數(shù)時,內(nèi)部函數(shù)中的 this 綁定會丟失,并且會綁定到全局對象或 undefined(嚴格模式下):
var lostObj5 = { name: 'lostObj5', fun: function() { function innerFun() { console.log(`Hello, ${this.name}!`); }; innerFun(); } }; lostObj5.fun();// Hello, undefined!
從結(jié)果可以發(fā)現(xiàn),嵌套函數(shù) innerFun()
中的 this 此時是指向全局環(huán)境。所以從這個案例可以說明作用域鏈和 this 沒有關(guān)系,作用域鏈不影響 this 的綁定。
原因是當innerFun()
被調(diào)用時,是作為普通函數(shù)調(diào)用,不像 fun()
屬于對象 lostObj5
的內(nèi)部方法而調(diào)用,因此最后其內(nèi)部的 this 指向全局對象。
其實 this 丟失可以通過箭頭函數(shù)來解決,下面就來聊聊箭頭函數(shù)
四、箭頭函數(shù)中的 this
箭頭函數(shù)是 ES6 增加的一種編寫函數(shù)的方法,它用簡潔的方式來表達函數(shù)
語法:()=>{}
參數(shù):(): 函數(shù)的參數(shù),{}: 函數(shù)的執(zhí)行體
1. 箭頭函數(shù)中的 this 指向
箭頭函數(shù)中的this是在定義時確定的,它是繼承自外層詞法作用域。而不是在運行時才確定,如以下代碼:
var testObj2 = { name: "testObj2", fun: function(){ setTimeout(()=>{ console.log(this); }) } } var testObj3 = { name: "testObj3", fun: function(){ setTimeout(function(){ console.log(this); }) } } //即使獨立調(diào)用函數(shù),箭頭函數(shù)內(nèi)的this指向是在定義時就已經(jīng)確定 testObj2.fun();//testObj testObj3.fun();//window
實際上箭頭函數(shù)中沒有 this 綁定,它是繼承自外層作用域的 this 值。因此在許多情況下,箭頭函數(shù)能解決 this 在運行時函數(shù)的綁定問題。
2. 箭頭函數(shù)與普通函數(shù)中的 this 差異
從 上面的例子可以看出箭頭函數(shù)和普通函數(shù)在 this 的處理上存在很大的差異,主要有:
this 綁定方式
普通函數(shù)的 this 是在運行時確定的;箭頭函數(shù)的 this 值是函數(shù)定義好后就已經(jīng)確定,它繼承自包含箭頭函數(shù)的外層作用域
作用域
普通函數(shù)是具有動態(tài)作用域,其 this 值在運行時基于函數(shù)的調(diào)用方式動態(tài)確定。箭頭函數(shù)具有詞法作用域,其 this 值在定義時就已經(jīng)確定,并繼承外部作用域
綁定 this 的對象
普通函數(shù)中 this 可以通過函數(shù)的調(diào)用方式(如對象方法、構(gòu)造函數(shù)、函數(shù)調(diào)用等)來綁定到不同的對象,而箭頭函數(shù)沒有自己的 this 綁定;箭頭函數(shù)沒有自己的 this 綁定,它只能繼承外部作用域的 this 值,無法在運行時改變綁定對象,而且也無法通過顯式綁定來改變 this 的指向。
var testObj4 = { arrowFun: ()=>{ console.log(this); }, normalFun: function(){ console.log(this); } } //此時箭頭函數(shù)的this繼承全局上下文的this,顯式綁定無法修改箭頭函數(shù)中的this值 testObj4.arrowFun();//window testObj4.arrowFun.apply({});//window testObj4.normalFun();//testObj4 testObj4.normalFun.apply({});//{}
下面我們就可以解答引言中的問題 2 了。箭頭函數(shù)中的 this 指向其上層的作用域,也就是 getAction()
中的 this 值,而從隱式綁定調(diào)用規(guī)則,當前是 vue 實例調(diào)用 getTableData()
然后再調(diào)用 getAction()
,因此 this 值指向當前 vue 實例。
五、 this 中的面試題
手寫實現(xiàn)一個 bind 函數(shù)
通過分析 bind 函數(shù)的語法和參數(shù)來:function.bind(thisArg[, arg1[, arg2[, ...]]])
- 返回值是一個函數(shù)
- 參數(shù) thisArg 指向
我們暫時不考慮原型問題,實現(xiàn)如下代碼:
Function.prototype.mybind = function (thisArg) { //1.隱式綁定,當前的this指向目標函數(shù) var targetFn = this; //將參數(shù)列表轉(zhuǎn)換為數(shù)組,并刪除第一個參數(shù) var args = Array.prototype.slice.call(arguments, 1); //2.返回值一個函數(shù) return function bound() { var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); //解決返回函數(shù)使用new后,綁定this忽略問題 var _this = targetFn instanceof this ? this: thisArg; return targetFn.apply(thisArg, finalArgs) } } }
總結(jié)
文章回顧 this 的概念和 this 指向的判斷綁定規(guī)則,
- 首先是綁定規(guī)則:
- 獨立函數(shù)調(diào)用執(zhí)行時,使用默認綁定規(guī)則,this 指向 window
- 當函數(shù)作為對象方法被調(diào)用,使用隱式綁定規(guī)則,this 指向這個對象
- 當函數(shù)作為構(gòu)造方法時,使用 new 綁定規(guī)則,this 指向返回的對象
- apply/call/bind 要注意參數(shù)的傳遞和返回值不同
- 箭頭函數(shù)要看該箭頭函數(shù)在哪個作用域下,this 就指向誰
綁定規(guī)則的優(yōu)先級:
fn()(全局環(huán)境)(默認綁定)< obj.fn()(隱式綁定) < fn.call(obj)=fn.apply(obj) = fn.bind(obj)(顯式綁定)< new fn()
- 此外要注意綁定失效的情況,善用箭頭函數(shù)來保證 this 的指向穩(wěn)定
以上就是JavaScript中的this指向綁定規(guī)則及常見面試總結(jié)的詳細內(nèi)容,更多關(guān)于JavaScript this指向的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實現(xiàn)簡易的天數(shù)計算器實例【附demo源碼下載】
這篇文章主要介紹了JavaScript實現(xiàn)簡易的天數(shù)計算器,結(jié)合實例形式分析了javascript日期與時間計算的相關(guān)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2017-01-01完美實現(xiàn)js拖拽效果 return false用法詳解
這篇文章主要為大家詳細介紹了完美實現(xiàn)js拖拽效果的代碼,一起學(xué)習return false的用法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07javascript獲取鼠標位置部分的實例代碼(兼容IE,FF)
這篇文章介紹了javascript獲取鼠標位置部分的實例代碼,有需要的朋友可以參考一下2013-08-08JavaScript中的document.querySelector()方法使用詳解
HTML的DOM querySelector()方法可以不需要額外的jQuery等支持,也可以方便的獲取DOM元素,語法跟jQuery類似,這篇文章主要給大家介紹了關(guān)于JavaScript中document.querySelector()方法使用的相關(guān)資料,需要的朋友可以參考下2024-06-06