js中call、apply、bind到底有什么區別?bind返回的方法還能修改this指向嗎?


 壹 ❀ 引

同事最近在看angularjs源碼,被源碼中各種bind,apply弄的暈頭轉向;於是他問我,你知道apply,call與bind的區別嗎?我說apply與call是函數應用,指定this的同時也將方法執行,bind不同,它只是負責綁定this並返回一個新方法,不會執行。

他又問,那如果一個方法bind對象a后,再bind對象b,最后再bind對象c,此時執行函數this指向誰呢?(他經常問這種奇葩問題...);我不加思索的回答,是c吧;真的嗎?他反問到。

反問的一瞬間,我知道我應該是說錯了,寫了個小demo驗證下,果不其然,所以我覺得有必要詳細去了解下三個方法的區別了。

let o1 = {
    a: 1
};
let o2 = {
    a: 2
};
let o3 = {
    a: 3
};

function fn(b, c) {
    console.log(this.a);
};

let fn1 = fn.bind(o1);
let fn2 = fn1.bind(o2);
let fn3 = fn2.bind(o3);
fn3() //?

 貳 ❀ 函數調用與函數應用

我們知道執行一個函數有兩種方式,一種是常見的函數調用,第二種就是函數應用了。

從主動被動的關系去解釋,函數調用顯得更為被動,而函數應用就顯得十分主動了,我們來看個例子:

var name = "聽風是風",
    obj = {
        name: 'echo'
    };

function fn() {
    console.log(this.name);
};

//函數調用
fn() //聽風是風

//函數應用
fn.call(obj); //echo

 fn() 等同於window.fn(),本質上方法fn是被window調用,所以this指向了window,這就像古代的娃娃親,對於fn來說this被安排的明明白白,婚姻及其不自由。

fn.call()雖然也等同於window.fn.call(),但call為fn提供了改變this的機會,此時的this也就是obj對象。可以看到隨着思想的解放,fn有了選擇婚姻的權利,更為自由和幸福。

那么說到這你應該明白了,call()與apply()就是提供函數應用的方法,它們讓函數執行時的this指向更為靈活。

 叄 ❀ call與apply

站在函數應用的角度我們知道了call與apply的用途,那這兩個方法又有什么區別呢,其實區別就一點,參數傳遞方式不同。

call方法中接受的是一個參數列表,第一個參數指向this,其余的參數在函數執行時都會作為函數形參傳入函數

fn.call(this, arg1, arg2, ...);

而apply不同的地方是,除了第一個參數作為this指向外,其它參數都被包裹在一個數組中,在函數執行時同樣會作為形參傳入。

fn.apply(this, [arg1, arg2, ...]);

除此之外,兩個方法的效果完全相同:

let 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后並不會立馬執行,而是返回一個新的綁定函數

let o = {
    a: 1
};

function fn(b, c) {
    console.log(this.a + b + c);
};

let fn1 = fn.bind(o, 2, 3);

fn1();//6

還記得文章開頭我同事提的問題嗎,我之所以覺得bind多次后執行,this會指向最后一次bind的對象,是因為沒能正確理解bind返回函數的含義。

嘗試打印返回的新函數fn1,可以看到它並不是一個普通的function,而是一個bound function,簡稱綁定函數:

它的TargetFunction指向了bind前的的函數,BoundThis就是綁定的this指向,BoundArgs便是傳入的其它參數了。

當我們執行fn1時,就有點類似於TargetFunction.apply(BoundThis,BoundArgs)

我們可以得出一個結論,當執行綁定函數時,this指向與形參在bind方法執行時已經確定了,無法改變。

let o1 = {
    a: 1
};
let o2 = {
    a: 2
};

function fn(b, c) {
    console.log(this.a + b + c);
};

let fn1 = fn.bind(o1, 2, 3);
//嘗試再次傳入形參
fn1(4, 4); //6

//嘗試改變this
fn1.call(o2); //6

其實很好理解,當執行fn1時,本質上等於window.fn1(),如果this還能被改變,那this豈不是得指向window,那bind方法就沒太大意義了。

 伍 ❀ 應用

說到這,我們大概知道了這三個方法的作用與區別,那這三個方法有啥用?其實很常用。

我們都知道Math.max()方法能取到一組數字中的最大值,比如:

Math.max(1, 10); //10
Math.min(1, 10); //1

那我們如何利用此方法求數組中最大最小呢,這里就可以利用apply的特性了:

Math.max.apply(null, [1, 2, 10]); //10
Math.max.min(null, [1, 2, 10]); //1

在非嚴格模式下,當我們讓this指向null,或者undefined時,this本質上會指向window,不過這里的null就是一個擺設,我們真正想處理的其實是后面的數組。

還記得在考慮兼容情況下怎么判斷一個對象是不是數組嗎?再或者是不是一個函數?利用Object.prototype.toString()結合call方法就能解決這個問題:

let a = [];
let b = function () {};
Object.prototype.toString.call(a) === "[object Array]";//true
Object.prototype.toString.call(b) === "[object Function]";//true

 陸 ❀ 總

 那么到這里,我們一起理解了我同事提出的奇葩問題,為何bind多次后執行,函數this還是指向第一次bind的對象。

其次,除了函數調用,我們理解了函數應用的概念,也知道如何使用call和apply去實現一個函數應用,順便通過bind還知道了綁定函數(bound function)的概念。

所以到這里,你知道call、apply與bind的區別了嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM