apply call 和bind
容許爲不一樣的對象分配和調用屬於一個對象的函數/方法。同時它們能夠改變函數內 this 的指向。前端
apply 和 call 接收的參數形式不一樣git
apply 和 call 都是直接調用函數並獲得函數執行結果,而 bind 會返回待執行函數,須要再次調用github
咱們先建立一個對象 parentapp
const parent = { name: 'parent', sayPerson (age, addr) { return { name: this.name, age, addr } } }
顯然它具備 name 屬性,及方法 sayPerson,咱們如今能夠經過 parent.sayPerson()
來輸出該對象信息。框架
const person = parent.sayPerson(60, 'shenzhen'); // {name: "parent", age: 60, addr: "shenzhen"}
如今咱們再建立一個對象 son函數
const son = { name: 'son' }
咱們如今也想獲得 son 的信息,可是 son 對象沒有 sayPerson 函數怎麼辦?藉助已有的 parent 對象和 call 方法,咱們能夠這樣寫學習
const person = parent.sayPerson.call(son, 26, 'shenzhen'); // {name: "son", age: 26, addr: "shenzhen"}
能夠看出,經過調用 call 函數,咱們爲 son 對象分配了 sayPerson 方法並進行調用。實現了一個對象能夠調用不屬於它本身的方法,而且函數內的 this 指向該對象。apply 方法的用法其實同樣,只是傳參有些區別測試
const person = parent.sayPerson.call(son, [26, 'shenzhen']); // {name: "son", age: 26, addr: "shenzhen"}
bind 函數則不直接調用函數,而是返回待調用函數this
const sayPersonFn = parent.sayPerson.bind(son, 26, 'shenzhen'); const person = sayPersonFn(); // {name: "son", age: 26, addr: "shenzhen"}
以上就是三者的使用方法和區別,下面咱們來看看它們是如何實現的prototype
實現原理就是爲對象 obj 添加須要調用的方法,接着調用該方法(此時 this 指向 obj),調用事後再刪除該方法
簡單版
Object.prototype.callFn = function (...args) { // 第一個參數爲目標對象 const context = args[0]; args.shift(); // 爲對象賦值須要調用的方法 context.fn = this; // 調用該方法 context.fn(...args); // 刪除方法 delete context.fn; }
加上返回值
Object.prototype.callFn = function (...args) { // 第一個參數爲目標對象 const context = args[0]; args.shift(); // 爲對象賦值須要調用的方法 context.fn = this; // 調用該方法 const result = context.fn(...args); // 刪除方法 delete context.fn; return result; }
在測試中發現,咱們調用 call,若是第一個參數是 null 或者 undefined,那麼 call 將以全局 window 來調用方法,此時 this 也指向 window。若是第一個參數不是對象類型,則以空對象 {} 來調用方法。
Object.prototype.callFn = function (...args) { // 第一個參數爲目標對象 let context = args[0]; // undefined 和 null 指向 window if (context === null || context === undefined) { context = window; } // 不是對象類型則建立空對象 if (typeof context !== 'object') { context = {}; } args.shift(); // 爲對象賦值須要調用的方法 context.fn = this; // 調用該方法 const result = context.fn(...args); // 刪除方法 delete context.fn; return result; }
至此,咱們實現了一個完整的 call 方法。
既然和 call 只存在傳參的區別,那咱們只須要簡單修改下已實現的 call 方法便可。
Object.prototype.applyFn = function (...args) { let context = args[0]; if (context === null || context === undefined) { context = window; } if (typeof context !== 'object') { context = {}; } args.shift(); context.fn = this; // 和 call 存在差別的地方 const result = context.fn(...args[0]); delete context.fn; return result; }
在實現了 apply 和 call 的前提下,bind 的實現也比較簡單。
Object.prototype.bindFn = function (...args) { // 實際就是多包了層待執行函數 return () => { return this.applyFn(args[0], (args || []).slice(1)); } }
至於以 bind 方法返回的函數做爲構造函數來建立對象會存在的問題請參考JavaScript深刻之bind的模擬實現。
call apply bind
在工做中其實是比較常見的函數,特別是在一些框架或庫的源碼中,可是常常有人會混淆它們的用法。但願你們經過此篇文章能夠完全弄清它們的做用與區別,而且知道其實現原理,知其然知其因此然。
歡迎到前端學習打卡羣一塊兒學習~516913974