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