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