1.作用
call、apply、bind作用是改變函數執行的上下文,簡而言之就是改變函數運行時的this指向
那么什么情況下需要改變this的指向呢?下面舉個例子
var name = "lucy"; var obj = {name: "martin",say: function () {console.log(this.name);}, }; obj.say(); //martin,this指向obj setTimeout(obj.say, 0); //lucy,this指向window
從上面可以看到,正常情況下say方法輸出martin
但是我們把say放在setTimeout方法中,在定時器中是作為回調函數來執行的,因此回到主棧執行時是在全局執行上下文的環境中執行的,這時候this指向window,所以輸出lucy
我們實際需要的是this指向obj對象,這時候就需要改變this指向了
setTimeout(obj.say.bind(obj),0)
2.區別
下面再來看看apply、call、bind的使用
2.1.apply
apply接收1兩個參數,第一個參數是this指向,第二個參數是函數接收的參數,以數組的形式傳入
改變this指向后原函數會立即執行,且此方法只是臨時改變this指向一次
function fn(...args) {console.log(this, args); } let obj = {myname: "張三", }; fn.apply(obj, [1, 2]); //this會變成傳入的obj,傳入的參數必須是一個數組 fn(1, 2); //this會變成window,傳入的參數必須是一個數組
當第一個參數為null、undefined的時候,默認指向window(在瀏覽器中)
fn.apply(null, [1, 2]); //this會變成window,傳入的參數必須是一個數組 fn.apply(undefined, [1, 2]); //this會變成window,傳入的參數必須是一個數組
2.2.call
call方法的第一個參數也是this指向,后面傳入的是一個參數列表
跟apply一樣,改變this指向后原函數會立即執行,且此方法只是臨時改變this指向一次
function fn(...args) {console.log(this, args); } let obj = {myname: "張三", }; fn.call(obj, 1, 2); //this會變成傳入的obj,傳入的參數必須是一個一個的 fn(1, 2); //this會變成window
同樣的,當第一個參數為null、undefined的時候,默認指向window(在瀏覽器中)
fn.call(null, 1, 2); //this會變成window,傳入的參數必須是一個一個的 fn.call(undefined, 1, 2); //this會變成window,傳入的參數必須是一個一個的
2.3.bind
bind方法和call很相似,第一個參數也是this的指向,后面傳入的也是一個參數列表(但是這個參數列表可以分多次傳入)
改變this指向后不會立即執行,而是返回一個永久改變this指向的函數
function fn(...args) {console.log(this, args); } let obj = {myname: "張三", }; const bindFn = fn.bind(obj); //this指向會變成傳入的obj,bind不是立即執行需要執行一次 bindFn(1, 2); //this指向obj,傳入的參數必須是一個一個的 fn(1, 2); //this指向window
2.4.小結
從上面可以看到,apply、call。、biond三者的區別在于:
- 三者都可以改變函數的this對象指向
- 三者第一個參數都是this要指向的對象,如果沒有這個參數或參數為undefined或null,則默認指向全局window
- 三者都可以傳參,但是apply是數組,而call是參數列表,且apply和call是一次性傳入參數,而bind可以分為多次傳入
- bind是返回綁定this之后的參數,apply、call則是立即執行
3.實現
實現bind的步驟,我們可以分解成三部分:
- 修改this指向
- 動態傳遞參數1 // ?式?: 只在bind中傳遞函數參數
2 fn.bind(obj,1,2)()
3 // ?式?: 在bind中傳遞函數參數,也在返回函數中傳遞參數
4 fn.bind(obj,1)(2) - 兼容new關鍵字
整體實現代碼如下:
Function.prototype.myBind = function (context) {// 判斷調用對象是否為函數if (typeof this !== "function") {throw new TypeError("Error"); //判斷調用對象是否為函數}// 獲取參數const args = [...arguments].slice(1),fn = this;return function Fn() {// 根據調用方式,傳入不同綁定值return fn.apply(this instanceof Fn ? new fn(...arguments) : context,args.concat(...arguments));}; };