在以前一篇文章写了这三个参数的区别,可是其实面试更常考察如何实现。其实全部的原生函数的 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方案。 完