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

 壹 ❀ 引angularjs

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

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

反問的一瞬間,我知道我應該是說錯了,寫了個小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() //?

 貳 ❀ 函數調用與函數應用this

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

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

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被安排的明明白白,婚姻及其不自由。code

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

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

 叄 ❀ 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的區別了嗎?

相關文章
相關標籤/搜索