在以前一篇文章寫了這三個參數的區別,可是其實面試更常考察如何實現。其實全部的原生函數的 polyfill 如何實現,只須要考慮 4 點便可:javascript
call 的基本功能:html
call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。java
返回值git
簡單實現:github
Function.prototype.myCall = function(context = window, ...args) { context.fn = this; // 先將fn掛在context上, var res = context.fn(...args); // 而後經過context調用fn,使得fn中的this指向指到context上 delete context.fn; // 最後刪除掉context上的fn return res; // 返回原函數的返回值 };
上面爲了簡單,使用了 ES6 的剩餘參數和展開語法,基本用這個回答面試官就行了。固然,若是不讓使用剩餘參數,那就只能使用eval
或者new Function
的字符串拼接大法了,能夠參考這篇模板引擎。
再就是 fn 可能會和 context 重名,整一個不會重名的 uniqueID 掛上去,執行完畢後刪除。面試
以前提過 apply 和 call 區別,只有一些入參和性能上的區別。直接上代碼:app
Function.prototype.myApply = function(context = window, args) { context.fn = this; // 先將fn掛在context上, var res = context.fn(...args); // 而後經過context調用fn,使得fn中的this指向指到context上 delete context.fn; // 最後刪除掉context上的fn return res; // 返回原函數的返回值 };
bind 有點不同,它會返回一個綁定了 this 的函數。函數
bind()方法建立一個新的函數,在 bind()被調用時,這個新函數的 this 被 bind 的第一個參數指定,其他的參數將做爲新函數的參數供調用時使用。性能
Function.prototype.myBind = function(context, ...args) { var fn = this; var newFn = function(...restArgs) { // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去 return fn.call(context, ...args, ...restArgs); }; return newFn; };
上面的函數基本上覆蓋了大部分場景,可是不能支持new
調用——this
綁定函數自動適應於使用 new 操做符去構造一個由目標函數建立的新實例。當一個綁定函數是用來構建一個值的,原來提供的 this 就會被忽略。不過提供的參數列表仍然會插入到構造函數調用時的參數列表以前。
若是直接使用咱們上面所寫的bind
,就會返回
function Person(age, name) { this.name = name; this.age = age; } var Age18Person = Person.myBind(null, 18); var a = {}; var Age20Person = Person.myBind(a, 20); var p18 = new Age18Person("test18"); // newFn {} var p20 = new Age20Person("test20"); // newFn {} // a {name: "test20", age: 20} // window {name: "test18", age: 18}
顯然,返回了以newFn
生成的對象,而且,由於傳入的是null
,因此,對context
的賦值轉移到了window
。
這裏須要判斷是否被 new 調用,而後丟棄沒用的 context。
Function.prototype.myBind = function(context, ...args) { var fn = this; var newFn = function(...restArgs) { // 若是是new構造,則使用new構造的實例 if (new.target) { return fn.call(this, ...args, ...restArgs); } // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去 return fn.call(context, ...args, ...restArgs); }; return newFn; };
再次調用上面的new
構造,發現實例的原型不是指向咱們但願的 Person
var Age18Person = Person.myBind(null, 18); var p18 = new Age18Person("test18"); // newFn {} p instanceof Person; // false p instanceof Age18Person; // false
記錄一下原型鏈,再來一遍
Function.prototype.myBind = function(context, ...args) { var fn = this; var newFn = function(...restArgs) { // 若是是new構造,則使用new構造的實例 if (new.target) { return fn.call(this, ...args, ...restArgs); } // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去 return fn.call(context, ...args, ...restArgs); }; // 綁定原型鏈 newFn.prototype = this.prototype; return newFn; };
可是這裏還有個問題,若是改了Age18Person
的prototype
,也會影響到Person
的prototype
。
因此,須要作一箇中轉——
Function.prototype.myBind = function(context, ...args) { var fn = this; var newFn = function(...restArgs) { // 若是是new構造,則使用new構造的實例 if (new.target) { return fn.call(this, ...args, ...restArgs); } // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去 return fn.call(context, ...args, ...restArgs); }; var NOOP = function() {}; // 綁定原型鏈 NOOP.prototype = this.prototype; newFn.prototype = new NOOP(); return newFn; };
這樣基本上就算完成了,固然更推薦function-bind方案。 完