1. 程式人生 > >一文搞懂 this、apply、call、bind

一文搞懂 this、apply、call、bind

### 碼文不易,轉載請帶上本文連結,感謝~ https://www.cnblogs.com/echoyya/p/14506269.html [toc] ### this 的指向 **“this” 關鍵字允許在呼叫函式或方法時決定哪個物件應該是焦點。** 在JavaScript中**this**可以是全域性物件、當前物件或者任意物件,這完全取決於函式的呼叫方式,this 繫結的物件即函式執行的上下文環境,在 ES5 中,其實 this 的指向,始終堅持一個原理:**this 永遠指向最後呼叫它的那個物件**,正是由於呼叫`function`的物件不同,才導致了`this`的指向不同。 **關於this的指向及繫結,請關注博文** [JS五種繫結徹底弄懂this,預設繫結、隱式繫結、顯式繫結、new繫結、箭頭函式繫結詳解](https://www.cnblogs.com/echoyya/p/14506742.html) ``` // e.g.1 var test = { a: 5, b: 6, sum: function (a, b) { function getA(a) { this.a = a; // 在window上增加了一個全域性變數a return this.a; // 此處this = window } function getB(b) { this.b = b; //在window上增加了一個全域性變數b return this.b; // 此處this = window } return getA(a) + getB(b); } } console.log(test.sum(4, 3)); // 7 console.log(a); // 4 (列印結果為 window.a) console.log(b); // 3 (列印結果為 window.b) ``` ``` // e.g.2 var user = { name: 'Echoyya', age: 27, greet() { console.log(this.name) }, bf: { name: 'KK.unlock', greet() { console.log(this.name) } } } user.greet() // Echoyya user.bf.greet() // KK.unlock ``` 如果 `greet` 函式不是 `user` 物件的函式,只是一個獨立的函式。 ``` function greet () { console.log(this.name) } var user = { name: 'Echoyya' } ``` 如何能讓 `greet` 方法呼叫的時候將 `this` 指向 `user` 物件?不能再像之前使用 `user.greet()`,因為 `user` 並沒有 `greet` 方法。我們可以通過一些方法去改變this的指向 ### 怎樣改變 this 的指向 #### (1)使用ES6中箭頭函式 **箭頭函式中的 this 始終指向函式定義時的 this,而非執行時**。箭頭函式中沒有 this 繫結,必須通過查詢**作用域鏈**來決定其值,如果箭頭函式被非箭頭函式包含,則 this 繫結的是最近一層非箭頭函式的 this,否則,this 為 undefined”。 也正因如此,箭頭函式不能用於建構函式 ``` var name = "windowsName"; var obj = { name: "Echoyya", func1: function () { console.log(this.name) }, func2: () => { console.log(this.name) } }; obj.func1() // Echoyya obj.func2() // windowsName ``` #### (2)函式內部使用 _this = this 如果不使用 ES6,那麼這種方式應該是最簡單且不易出錯的方式了,先將呼叫這個函式的物件儲存在變數 `_this` 中,然後在函式中都使用這個 `_this`。 `obj`呼叫`func`時 `this` 指向`obj`,防止setTimeout 被 window 呼叫,導致 setTimeout 中的 this 指向 window,設定 `var _this = this`,將 `this(指向變數 obj)` 賦值給一個變數 `_this`,這樣,在 `func ` 中使用`_this` 就指向物件 `obj` ``` var name = "windowsName"; var obj = { name: "Echoyya", func: function () { var _this = this; setTimeout(function () { console.log(_this.name) }, 1000); } }; obj.func() // Echoyya ``` #### (3)使用call,apply,bind方法 `call、apply、bind` 方法是每個函式都有的一個方法,允許在呼叫函式時為函式指定上下文。可以改變 this 指向 ##### call 與 apply - `call` 方法,第一個引數會作為函式被呼叫時的上下文。即`this` 指向傳給 `call` 的第一個引數。 - 傳遞引數給使用 `.call` 呼叫的函式時,需要在指定上下文(第一個引數)後一個一個地傳入。 - 語法:`fun.call(thisArg [, arg1[, arg2[, ...]]])` 站在函式應用的角度我們知道了call與apply的用途,那這兩個方法又有什麼區別呢,其實區別就一點,引數傳遞方式不同。 1. call方法中接受的是一個引數列表,第一個引數指向this,其餘的引數在函式執行時都會作為函式形參傳入函式。 - 語法:`fn.call(this, arg1, arg2, ...);` 2. 而apply不同的地方是,除了第一個引數作為this指向外,其它引數都被包裹在一個數組中,在函式執行時同樣會作為形參傳入。 - 語法:`fn.apply(this, [arg1, arg2, ...]);` 除此之外,兩個方法的效果完全相同: ``` var o = { a: 1 }; function fn(b, c) { console.log(this.a + b + c); }; fn.call(o, 2, 3); // 6 fn.apply(o, [2, 3]); //6 ``` ##### 關於 bind bind要單獨說一下,因為它與call,apply還不太一樣。call與apply在改變this的同時,就立刻執行,而bind繫結this後並不會立馬執行,而是返回一個新的繫結函式,需要在執行一下。 ``` var o = { a: 1 }; function fn(b, c) { console.log(this.a + b + c); }; var fn2 = fn.bind(o, 2, 3); fn2(); //6 ``` ``` var name = 'windowsName' function greet () { console.log(this.name) } var user = { name: 'Echoyya' } greet() // windowsName greet.bind()() // windowsName (非嚴格模式下,預設指向window) greet.bind(user)() // Echoyya ``` ``` var obj = { name: "Echoyya", func: function () { setTimeout(function () { console.log(this.name) }.bind(obj)(), 100); } }; obj.func() // Echoyya ``` ##### call、apply、bind 區別 call、apply、bind 都是可以改變 this 的指向的,但是這三個函式稍有不同。 ``` var test = { a: 5, b: 6, sum: function (a, b) { var self = this; function getA() { return self.a; } function getB() { return self.b; } console.log(a, b); // call, apply, bind 傳入引數 return getA() + getB(); } } var obj = { a: 2, b: 3 }; console.log(test.sum.call(obj, 4, 5)); // 4,5,5 (self = this = obj) console.log(test.sum.apply(obj, [6, 7])); // 6,7,5 (self = this = obj) console.log(test.sum.bind(obj, 8, 9)()); // 8,9,5 (self = this = obj) ``` > **apply 和 call 的區別** - `.apply` 和 `.call` 本質相同,他們的區別只是傳入的引數不同。 - 傳遞引數給使用 `.call` 呼叫的函式時,需要在指定上下文(第一個引數)後一個一個地傳入(**引數列表**)。 - 傳遞引數給使用 `.apply` 呼叫的函式時,可以用陣列傳參而且 `.apply` 會在函式中自動展開(**引數陣列**)。 > **call、apply、bind到底有什麼區別?bind返回的方法還能修改this指向嗎?** 1. `apply與call是函式應用,指定this的同時也將方法執行,bind不同,它只是負責繫結this並返回一個新方法,不會執行。` 2. 嘗試列印返回的新函式fn2,可以看到它並不是一個普通的function,而是一個`bound function`,簡稱`繫結函式`: - `TargetFunction 指向 bind 前的函式`; - `BoundThis 是繫結的 this 指向`; - `BoundArgs 是傳入的其它引數了`。 ![](https://img2020.cnblogs.com/blog/1238759/202103/1238759-20210309185557460-2069821733.png) 3. 當我們執行fn2時,有點類似於`TargetFunction.apply(BoundThis,BoundArgs)`。可以得出一個結論,當`執行繫結函式時,this指向與形參在bind方法執行時已經確定了,無法改變。` 4. **bind多次後執行,函式this還是指向第一次bind的物件。** ``` var o1 = { a: 1 }; var o2 = { a: 2 }; function fn(b, c) { console.log(this.a + b + c); }; var fn1 = fn.bind(o1, 2, 3); //嘗試再次傳入形參 fn1(4, 4); //6 //嘗試改變this fn1.call(o2); //6 //嘗試再次bind fn1.bind(o2, 1, 1)() // 6 ``` 其實很好理解,當執行fn1時,本質上等於window.fn1(),如果this還能被改變,那this豈不是得指向window,那bind方法就沒太大意