实现 call、apply、bind

在之前一篇文章写了这三个参数的区别,但是其实面试更常考察如何实现。其实所有的原生函数的 polyfill 如何实现,只需要考虑 4 点即可:

  1. 基本功能
  2. 原型
  3. this
  4. 返回值

call

  1. call 的基本功能:

    call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

  2. 原型

    不涉及原型链的转移,不用管
  3. this

    本质上,call 就是 this 的转移
  4. 返回值

简单实现:

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

之前提过 apply 和 call 区别,只有一些入参和性能上的区别。直接上代码:

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

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调用——

绑定函数自动适应于使用 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;
};

但是这里还有个问题,如果改了Age18Personprototype,也会影响到Personprototype

所以,需要做一个中转——

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方案。

最新文章

  1. cookie属性详解
  2. @Html.Partials 加载分布视图传参数
  3. linux_脚本应用
  4. MySQL Server 5.7解压版缺少文件无法启动
  5. SICP 习题 (1.7) 解题总结
  6. 济南学习 Day 5 T1 pm
  7. Cortex依赖管理
  8. CF 316C2(Tidying Up-二分图最大边权)
  9. 有趣 IOS 开展 - block 使用具体解释
  10. 每天点滴的进行,css+div简单布局...布局
  11. 10本Java书籍推荐
  12. 第一篇-Win10打开txt文件出现中文乱码
  13. sass用法快速入门
  14. The MathType Dll cannot be found 问题解决办法
  15. Windows-universal-samples学习笔记系列三:Navigation
  16. ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现
  17. HDU 3999 The order of a Tree (先序遍历)
  18. 在Linux上yum安装运行Redis,只能安装2.4.10(主从)
  19. 在 Ubuntu 16.04 LTS 上 离线安装 Docker / Docker-compose
  20. solrCloud源码分析之CloudSolrClient

热门文章

  1. PHP 插入排序 -- 希尔排序
  2. bcache 状态/配置 文件详细介绍(翻译自官网)
  3. 关于vue使用的一些小经验
  4. 浏览器标签tab窗口切换时事件状态侦听
  5. spring security原理-学习笔记1-整体概览
  6. V2er - Best client for V2EX
  7. 分布式监控CAT服务端的本地部署
  8. 线性回归 python 代码实现
  9. 设计模式(十三)Visitor模式
  10. Java基础(二十)集合(2)Collection接口