javascript
JS中this的应用场景,再了解下apply、call和bind!
this的應(yīng)用場景,再了解下apply、call和bind
- 一、談?wù)剬his對象的理解
- 二、this的應(yīng)用場景
- 1、作為普通函數(shù)被調(diào)用
- 2、使用call、apply和bind被調(diào)用
- 3、作為對象方法被調(diào)用
- 4、在class方法中被調(diào)用
- 5、箭頭函數(shù)中被調(diào)用
- 三、apply、call和bind
- 1、apply、call和bind的共同用法
- 2、apply
- 3、call
- 4、bind
- 5、做個小結(jié)
- 四、寫在最后
在寫程序時,我們都知道this很好用,但是卻很容易導(dǎo)致亂用。就像我剛開始學(xué)習(xí)箭頭函數(shù)時,我知道這個箭頭指代的是this,但是卻不知道它往哪里指,所以在寫程序時,就會想當(dāng)然的亂寫,導(dǎo)致有時候因為一個數(shù)據(jù)獲取不到而瘋狂找錯,這無形之中要增加很大的時間成本,不懂原理胡來總是很容易事后兩行淚(T_T)
在下面的這邊文章中,將講解關(guān)于this的幾大應(yīng)用場景以及了解在面試中經(jīng)常會被問到的apply、bind和call究竟是什么,接下來開始進(jìn)入本文的講解。
一、談?wù)剬his對象的理解
-
this ,函數(shù)執(zhí)行的上下文,總是指向函數(shù)的直接調(diào)用者(而非間接調(diào)用者),可以通過 apply , call , bind 改變 this 的指向。
-
如果有 new 關(guān)鍵字,this 指向 new 出來的那個對象。
-
在事件中,this 指向觸發(fā)這個事件的對象,特殊的是,IE 中的 attachEvent 中的 this 總是指向全局對象 window 。
-
對于匿名函數(shù)或者直接調(diào)用的函數(shù)來說,this 指向全局上下文(瀏覽器為 window ,NodeJS 為 global),剩下的函數(shù)調(diào)用,那就是誰調(diào)用它, this 就指向誰。
-
對于 es6 的箭頭函數(shù),箭頭函數(shù)的指向取決于該箭頭函數(shù)聲明的位置,在哪里聲明, this 就指向哪里。
二、this的應(yīng)用場景
在程序中,this主要有以下5種應(yīng)用場景:
- 作為普通函數(shù)被調(diào)用
- 使用 call 、 apply 和 bind 被調(diào)用
- 作為對象方法被調(diào)用
- 在 class 方法中被調(diào)用
- 箭頭函數(shù)中被調(diào)用
1、作為普通函數(shù)被調(diào)用
當(dāng) this 作為普通函數(shù)被調(diào)用時,指向 window 全局。
function fn1(){console.log(this); }fn1(); //window2、使用call、apply和bind被調(diào)用
當(dāng) this 使用 call 、 apply 和 bind 被調(diào)用時,直接指向作用域內(nèi)的內(nèi)容。
function fn1(){console.log(this); }fn1(); //windowfn1.call({ x : 100 }); //{x : 100} fn1.apply({x : 200}); //{x : 200}const fn2 = fn1.bind({ x : 200 }); fn2(); //{ x : 200 }3、作為對象方法被調(diào)用
從下面代碼中可以得出,當(dāng) this 放在 sayHi() 方法里面時,此時作為 zhangsan 對象的方法被調(diào)用,指向的是當(dāng)前的對象。而放在 wait() 方法時,里面還有一個定時器,定時器里面還有一個函數(shù),所以第二個 this 是作為普通函數(shù)被調(diào)用,指向 window 全局。
const zhangsan = {name: '張三',sayHi(){//this 即當(dāng)前對象console.log(this);},wait(){setTimeout(function(){//this === windowconsole.log(this);});} }4、在class方法中被調(diào)用
從以下代碼中可以看出,當(dāng) this 在 class 中被調(diào)用時,指向的是整個對象。
class People{constructor(name){this.name = name;this.age = 20;}sayHi(){console.log(this);} }const zhangsan = new People('張三'); zhangsan.sayHi(); //zhangsan 對象5、箭頭函數(shù)中被調(diào)用
看到以下代碼,細(xì)心的小伙伴不難發(fā)現(xiàn),跟我們上面第3點看到的似乎有點類似,主要區(qū)別在于定時器中的函數(shù)改為了箭頭函數(shù)。當(dāng)改為箭頭函數(shù)時,此時的this指向的是zhangsan這一個整個對象,而不再是指向全局。
const zhangsan = {name: '張三',sayHi(){//this 即當(dāng)前對象console.log(this);},waitAgain(){setTimeout(() => {//this 即當(dāng)前對象console.log(this);});} }講完箭頭函數(shù),我們來梳理下箭頭函數(shù)和普通函數(shù)的區(qū)別,以及箭頭函數(shù)是否能當(dāng)做是構(gòu)造函數(shù)的問題。
(1)箭頭函數(shù)和普通函數(shù)定義
普通函數(shù)通過 function 關(guān)鍵字定義, this 無法結(jié)合詞法作用域使用,在運行時綁定,只取決于函數(shù)的調(diào)用方式,在哪里被調(diào)用,調(diào)用位置。(取決于調(diào)用者,和是否獨立運行)
箭頭函數(shù)使用被稱為 “胖箭頭” 的操作 => 定義,箭頭函數(shù)不應(yīng)用普通函數(shù) this 綁定的四種規(guī)則,而是根據(jù)外層(函數(shù)或全局)的作用域來決定 this ,且箭頭函數(shù)的綁定無法被修改( new 也不行)。
(2)箭頭函數(shù)和普通函數(shù)的區(qū)別
- 箭頭函數(shù)常用于回調(diào)函數(shù)中,包括事件處理器或定時器。
- 箭頭函數(shù)和 var self = this ,都試圖取代傳統(tǒng)的 this 運行機制,將 this 的綁定拉回到詞法作用域。
- 箭頭函數(shù)沒有原型、沒有 this 、沒有 super,沒有 arguments ,沒有 new.target。
- 箭頭函數(shù)不能通過 new 關(guān)鍵字調(diào)用。
- 一個函數(shù)內(nèi)部有兩個方法:[[Call]] 和 [[Construct]],在通過 new 進(jìn)行函數(shù)調(diào)用時,會執(zhí)行 [[construct]] 方法,創(chuàng)建一個實例對象,然后再執(zhí)行這個函數(shù)體,將函數(shù)的 this 綁定在這個實例對象上。
- 當(dāng)直接調(diào)用時,執(zhí)行 [[Call]] 方法,直接執(zhí)行函數(shù)體。
- 箭頭函數(shù)沒有 [[Construct]] 方法,不能被用作構(gòu)造函數(shù)調(diào)用,當(dāng)使用 new 進(jìn)行函數(shù)調(diào)用時會報錯。
(3)this綁定的四大規(guī)則
this綁定四大規(guī)則遵循以下順序:
New 綁定 > 顯示綁定 > 隱式綁定 > 默認(rèn)綁定
下面一一介紹四大規(guī)則。
- 默認(rèn)綁定:沒有其他修飾( bind 、 apply 、 call ),在非嚴(yán)格模式下定義指向全局對象,在嚴(yán)格模式下定義指向 undefined 。
- 隱式綁定:調(diào)用位置是否有上下文對象,或者是否被某個對象擁有或者包含,那么隱式綁定規(guī)則會把函數(shù)調(diào)用中的 this 綁定到這個上下文對象。而且,對象屬性鏈只有上一層或者最后一層在調(diào)用位置中起作用。
- 顯式綁定:通過在函數(shù)上運行 call 和 apply ,來顯式的綁定 this 。
顯示綁定之硬綁定
function foo(something) { console.log(this.a, something); return this.a + something; } function bind(fn, obj) { return function() {return fn.apply(obj, arguments); }; } var obj = { a: 2 } var bar = bind(foo, obj); console.log(bar); //f()- New 綁定:new 調(diào)用函數(shù)會創(chuàng)建一個全新的對象,并將這個對象綁定到函數(shù)調(diào)用的 this。New 綁定時,如果是 new 一個硬綁定函數(shù),那么會用 new 新建的對象替換這個硬綁定 this 。
三、apply、call和bind
1、apply、call和bind的共同用法
先說下三者的共同用法,三者的共同用法就是可以改變函數(shù)的this指向,并將函數(shù)綁定到上下文中。接下來講述一個應(yīng)用場景加深理解:
let obj1 = {hobby: 'running',add(favorite){console.log(`在我的業(yè)余時間里,我喜歡${favorite},但同時我也喜歡${this.hobby}`);} }let obj2 = {hobby: 'learning' }obj1.add('reading'); //在我的業(yè)余時間里,我喜歡reading,但同時我也喜歡running可以看到在最后一行代碼中,我們調(diào)用了 obj1 中的 add 函數(shù),并傳入了一個參數(shù) reading 。 add 函數(shù)中的 this 指的是他所在的對象 obj1 ,所以 this.hobby 就是 running , 但是我們?nèi)绻氆@得 obj2 中的hobby, 又該怎么處理呢?這就涉及到我們平常所聽到的 apply 、 call 和 bind 。
接下來開始講解 apply 、 call 和 bind 。
2、apply
(1)語法: Array.prototype.apply(this, [args1, args2]) 。
(2)傳入?yún)?shù):
第一個參數(shù):傳入 this 需要指向的對象,即函數(shù)中的 this 指向誰,就傳誰進(jìn)來;
第二個參數(shù):傳入一個數(shù)組,數(shù)組中包含了函數(shù)需要的實參。
(3)apply的作用:①調(diào)用函數(shù);②指定函數(shù)中 this 的指向。
(4)代碼演示:
/*** * @description 實現(xiàn)apply函數(shù),在函數(shù)原型上封裝myApply函數(shù), 實現(xiàn)和原生apply函數(shù)一樣的效果*/Function.prototype.myApply = function(context){// 存儲要轉(zhuǎn)移的目標(biāo)對象_this = context ? Object(context) : window;// 在轉(zhuǎn)移this的對象上設(shè)定一個獨一無二的屬性,并將函數(shù)賦值給它let key = Symbol('key');_this[key] = this;// 將數(shù)組里存儲的參數(shù)拆分開,作為參數(shù)調(diào)用函數(shù)let res = arguments[1] ? _this[key](...arguments[1]) : _this[key]();// 刪除delete _this[key];// 返回函數(shù)返回值return res; }(5)前情回顧
實現(xiàn)了 myApply 之后,我們繼續(xù)引用剛開始關(guān)于愛好的那個例子,來修改 this 的指向。
let obj1 = {hobby: 'running',add(...favorite){ //...favorite意味著可以接收多個參數(shù)console.log(`在我的業(yè)余時間里,我喜歡${favorite},但同時我也喜歡${this.hobby}`);} }let obj2 = {hobby: 'learning' }obj1.add.myApply(obj2, ['reading', 'working']); // 輸出結(jié)果:在我的業(yè)余時間里,我喜歡reading,working,但同時我也喜歡learning在 obj1.add.myApply(obj2, ['reading', 'working']) 這一行代碼, 第一個參數(shù)將 obj1 中的 add 函數(shù)的 this 指向了 obj2 , 第二個參數(shù)以數(shù)組形式傳入多個參數(shù),作為 obj1 中的 add 函數(shù)傳入的參數(shù), 所以最后能將 reading 和 working 都輸出。
3、call
(1)語法: Array.prototype.call(this, args1, args2)
(2)傳入?yún)?shù):
第一個參數(shù):傳入 this 需要指向的對象,即函數(shù)中的 this 指向誰,就傳誰進(jìn)來;
其余參數(shù): 除了第一個參數(shù),其他的參數(shù)需要傳入幾個,就一個一個傳遞進(jìn)來即可。
(3)call的作用:①調(diào)用函數(shù);②指定函數(shù)中 this 的指向。
(4)代碼演示:
/*** * @description 實現(xiàn)apply函數(shù),在函數(shù)原型上封裝myApply函數(shù), 實現(xiàn)和原生apply函數(shù)一樣的效果*/Function.prototype.myCall = function(context){// 存儲要轉(zhuǎn)移的目標(biāo)對象let _this = context ? Object(context) : window;// 在轉(zhuǎn)移this的對象上設(shè)定一個獨一無二的屬性,并將函數(shù)賦值給它let key = Symbol('key');_this[key] = this;// 創(chuàng)建空數(shù)組,存儲多個傳入?yún)?shù)let args = [];// 將所有傳入的參數(shù)添加到新數(shù)組中for(let i =1; i < arguments.length; i++){args.push(arguments[i]);}// 將新數(shù)組拆開作為多個參數(shù)傳入,并調(diào)用函數(shù)let res = _this[key](...args);// 刪除delete _this[key];// 返回函數(shù)返回值return res; }(5)前情回顧
實現(xiàn)了 myCall 之后,我們繼續(xù)引用剛開始關(guān)于愛好的那個例子,來修改 this 的指向。
let obj1 = {hobby: 'running',add(...favorite){ //...favorite意味著可以接收多個參數(shù)console.log(`在我的業(yè)余時間里,我喜歡${favorite},但同時我也喜歡${this.hobby}`);} }let obj2 = {hobby: 'learning' }obj1.add.myCall(obj2, 'reading', 'working');// 輸出結(jié)果:在我的業(yè)余時間里,我喜歡reading,working,但同時我也喜歡learning在 obj1.add.myCall(obj2, 'reading', 'working') 這一行代碼, 第一個參數(shù)將 obj1 中的 add 函數(shù)的 this 指向了 obj2 , 第二個參數(shù)通過依次傳入多個參數(shù)的形式,作為 obj1 中的 add 函數(shù)傳入的參數(shù), 所以最后能將 reading 和 working 都輸出。
講到這里,我們來梳理下 call 和 apply 的區(qū)別:
call 和 apply 唯一的區(qū)別就是在于給函數(shù)傳入?yún)?shù)的形式不同, call 是將多個參數(shù)逐個傳入, 而apply 是 將多個參數(shù)放在一個數(shù)組中,一起傳入。
4、bind
(1)語法: Array.prototype.bind(this, args1, args2) 。
(2)傳入?yún)?shù):
第一個參數(shù):傳入 this 需要指向的對象,即函數(shù)中的 this 指向誰,就傳誰進(jìn)來;
其余參數(shù): 除了第一個參數(shù),其他參數(shù)的傳遞可以像 apply 一樣的數(shù)組類型,也可以像 call 一樣的逐個傳入;但需注意的是后面需要加個小括號進(jìn)行其余參數(shù)的傳遞。
(3)call的作用:①克隆當(dāng)前函數(shù),返回克隆出來的新函數(shù);②新克隆出來的函數(shù),該函數(shù)的this被指定了。
(4)代碼演示:
/*** @description 實現(xiàn)Bind函數(shù),在函數(shù)原型上封裝myBind函數(shù) , 實現(xiàn)和原生bind函數(shù)一樣的效果* */Function.prototype.myBind = function(context){// 存儲要轉(zhuǎn)移的目標(biāo)對象let _this = context ? Object(context) : window;// 在轉(zhuǎn)移this的對象上設(shè)定一個獨一無二的屬性,并將函數(shù)賦值給它let key = Symbol('key');_this[key] = this;// 創(chuàng)建函數(shù)閉包return function(){// 將所有參數(shù)先拆分開,再添加到新數(shù)組中,以此來支持多參數(shù)傳入以及數(shù)組參數(shù)傳入的需求let args = [].concat(...arguments);// 調(diào)用函數(shù)let res = _this[key](...args);// 刪除delete _this[key];// 返回函數(shù)返回值return res;} }(5)前情回顧
實現(xiàn)了 myBind 之后,我們繼續(xù)引用剛開始關(guān)于愛好的那個例子,來修改 this 的指向。
let obj1 = {hobby: 'running',add(...favorite){ //...favorite意味著可以接收多個參數(shù)console.log(`在我的業(yè)余時間里,我喜歡${favorite},但同時我也喜歡${this.hobby}`);} }let obj2 = {hobby: 'learning' }obj1.add.myBind(obj2)(['reading', 'working']);// 輸出結(jié)果:在我的業(yè)余時間里,我喜歡reading,working,但同時我也喜歡learning通過以上我們可以看到, bind 有點類似 apply 和 call 的結(jié)合,只不過它返回的是一個函數(shù),需要自身再進(jìn)行一次調(diào)用, 而傳給這個函數(shù)的參數(shù)形式有兩種方式,可以是像 apply 一樣的數(shù)組形式, 也可以是像 call 一樣的逐個傳入的形式。
大家不要覺得這個后面加個小括號太麻煩,這就是 bind 的強大之處,有時候 bind也會經(jīng)常運用在函數(shù)柯里化中。
講到這里,關(guān)于this的相關(guān)知識就講完啦!接下來我們來做個總結(jié)。
5、做個小結(jié)
-
this 取什么樣的值,是在函數(shù)執(zhí)行時確定的,不是在函數(shù)定義的時候確定的。
-
apply 、call 、bind 三者都是函數(shù)的方法,都可以改變函數(shù)的 this 指向。
-
apply 和 call 都是改變函數(shù) this 指向,并傳入?yún)?shù)后立即調(diào)用執(zhí)行該函數(shù)。
-
bind 是在改變函數(shù) this 指向后,并傳入?yún)?shù)后返回一個新的函數(shù),不會立即調(diào)用執(zhí)行。
-
apply 傳入的參數(shù)是數(shù)組形式的,call 傳入的參數(shù)是按順序的逐個傳入并以逗號隔開, bind 傳入的參數(shù)既可以是數(shù)組形式,也可以是按順序逐個傳入。
四、寫在最后
關(guān)于 this 的指向問題在前端的面試中尤為常見,大家可以按照上文中的順序把 this 的知識點串聯(lián)起來一起理解!同時,本文內(nèi)容為本人理解所整理,可能會存在邊界歧義等問題。如果有不理解或者有誤的地方歡迎私聊我或加我微信指正~
- 公眾號:星期一研究室
- 微信:MondayLaboratory
如果這篇文章對你有用,記得點個贊再走哦~
總結(jié)
以上是生活随笔為你收集整理的JS中this的应用场景,再了解下apply、call和bind!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 香菇干的功效与作用、禁忌和食用方法
- 下一篇: ofstream和ifstream详细用