Function.prototype.bind 函数,参见ECMA规范地址

如题,这次来实现一个boundFunction函数,不挂载在Function.prototype上,而是一个单独声明的函数。挂载在原型上的bind,可以参考MDN

主要步骤,摘自ECMA规范,如图:

实现思想:当然是依葫芦画瓢,这里,我们借用ES6的...运算符与解构赋值。目的是图省事,实现bind函数,主要是了解其内部的操作流程。

首先,把需要用到的函数,都依照规范声明实现,其中FunctionIsConstructor是自己写的判断一个函数是否为构造函数,比如Proxy就不是构造函数。

而SetFunctionLength是对设置函数length属性的操作的封装,正如其名。

  1. function FunctionIsConstructor(fnc) {
  2. let isConstructor = true;
  3. try {
  4. Object instanceof fnc
  5. } catch (e) {
  6. if (e instanceof TypeError) {
  7. isConstructor = false
  8. }
  9. }
  10. return isConstructor
  11. }
  12. function BoundFunctionCreate(targetFunction, boundThis, boundArgs) {
  13. let proto = Object.getPrototypeOf(targetFunction);
  14. let boundFunction = function () {
  15. if (new.target) {
  16. // 实现构造函数功能
  17. if (FunctionIsConstructor(targetFunction)) {
  18. return new targetFunction(...boundArgs)
  19. } else {
  20. throw new TypeError(`${arguments.callee.name} is not a constructor`)
  21. }
  22. } else {
  23. // 实现函数调用功能
  24. return targetFunction.call(boundThis, [...boundArgs, ...arguments])
  25. }
  26. }
  27. delete boundFunction.name;
  28. Object.setPrototypeOf(boundFunction, proto)
  29. return boundFunction;
  30. }
  31. function isCallable(Target) {
  32. if (typeof Target === 'function') return true;
  33. return false;
  34. }
  35. function ToInteger(arg) {
  36. let number = Number(arg);
  37. if (number !== number) return +0;
  38. if (number === 0 || number === Infinity || number === -Infinity) return number;
  39. return Math.floor(Math.abs(number));
  40. }
  41. function SetFunctionName(F, name, prefix) {
  42. if (typeof name === 'symbol') {
  43. let description = name.description
  44. if (description === undefined) {
  45. name = ''
  46. } else {
  47. name = `[${description}]`
  48. }
  49. }
  50. if (prefix) {
  51. name = `${prefix} ${name}`
  52. }
  53. return Object.defineProperty(F, 'name', {
  54. value: name,
  55. writable: false,
  56. enumerable: false,
  57. configurable: true
  58. })
  59. }
  60. function SetFunctionLength(F, Target, args) {
  61. let targetHasLength = Target.hasOwnProperty('length');
  62. let L;
  63. if (targetHasLength) {
  64. let targetLen = Target.length;
  65. if (typeof targetLen !== 'number') {
  66. L = 0;
  67. } else {
  68. targetLen = ToInteger(targetLen)
  69. L = Math.max(0, targetLen - args.length)
  70. }
  71. } else {
  72. L = 0;
  73. }
  74. Object.defineProperty(F, 'length', {
  75. value: L,
  76. writable: false,
  77. enumerable: false,
  78. configurable: true
  79. })
  80. }

然后,把这些函数按照规范的流程,组装起来,完全对应。

  1. function boundFuntion(targetFunction, thisArg, ...args) {
  2. let Target = targetFunction;
  3. if (!isCallable(Target)) {
  4. throw new TypeError(`${Target.name}.bind is not a function`)
  5. }
  6. let F = BoundFunctionCreate(Target, thisArg, args);
  7. SetFunctionLength(F, Target, args)
  8. let targetName = Target.name
  9. if (typeof targetName !== 'string') targetName = '';
  10. SetFunctionName(F, targetName, 'bound')
  11. // 支持直接new调用创建的绑定函数
  12. return new.target ? new F() : F
  13. }

如此,一个手写的bind函数就出来。函数最后一行,用new.target来判断,以支持直接使用new调用创建的绑定函数,如new boundFunction(fnc)

最后,简单测试一下。

  1. var modules = {
  2. x: 42,
  3. getX: function() {
  4. console.log('this', this === modules, this.x)
  5. return this.x;
  6. }
  7. }
  8.  
  9. var unboundGetX = modules.getX;
  10. console.log('unbounnd ', unboundGetX()); // The function gets invoked at the global scope
  11. // // expected output: unbounnd undefined
  12.  
  13. var boundGetX = boundFuntion(unboundGetX, modules);
  14. console.log('bounnd ', boundGetX());
  15. // expected output: bounnd 42

总结

手写bind函数,主要是利用闭包功能,将传入的this固定在新函数里,并对原型链进行处理,以免丢失继承关系。

其他的错误处理,比如判断是否构造函数,是否用new调用当前函数,也是值得去了解的。

依据ECMA规范,手写一个bind函数的更多相关文章

  1. 手写Function.bind函数

    if(!Function.prototype.bind){ Function.prototype.bind = function(oThis){ if(typeof this !=="fun ...

  2. 手写一个bind

    1 Function.prototype.bind1 = function(){ 2 // 将类数组转化成数组 3 let arr = Array.prototype.slice.call(argum ...

  3. 手写简化版printf函数

    2019.02.01更新:经同学提醒,myprintf函数应有返回值为输出的字符数. 期末的大作业,手写一个myprintf函数,支持如下一些操作. 也就是  % -(负号控制左右对齐) 数(控制字段 ...

  4. 只会用就out了,手写一个符合规范的Promise

    Promise是什么 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可以获取异步操作的消息.Prom ...

  5. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  6. 『练手』手写一个独立Json算法 JsonHelper

    背景: > 一直使用 Newtonsoft.Json.dll 也算挺稳定的. > 但这个框架也挺闹心的: > 1.影响编译失败:https://www.cnblogs.com/zih ...

  7. 手写事件代理函数 (Delegated function)

    ‘手写 ’ 这个词 ,面试是不是听过无数遍呢 ! 今天我们来手写一个这样的事件委托函数 => function( parent, selector, type ,  handle)  {} 你需 ...

  8. 看年薪50W的架构师如何手写一个SpringMVC框架

    前言 做 Java Web 开发的你,一定听说过SpringMVC的大名,作为现在运用最广泛的Java框架,它到目前为止依然保持着强大的活力和广泛的用户群. 本文介绍如何用eclipse一步一步搭建S ...

  9. 用过消息队列?Kafka?能否手写一个消息队列?懵

    是否有同样的经历?面试官问你做过啥项目,我一顿胡侃,项目利用到了消息队列,kafka,rocketMQ等等. 好的,那请开始你的表演,面试官递过一支笔:给我手写一个消息队列!!WHAT? 为了大家遇到 ...

随机推荐

  1. 查看linux空间大小

    du -sh : 查看当前目录总共占的容量.而不单独列出各子项占用的容量 du -lh --max-depth=1 : 查看当前目录下一级子文件和子目录占用的磁盘容量.

  2. CentOS 7 yum方式快速安装MongoDB

    一.安装环境及配置yum # more /etc/redhat-release CentOS Linux release 7.2.1511 (Core) # vi /etc/yum.repos.d/m ...

  3. Selector-背景选择器

    Selector-背景选择器 1.主要属性 <?xml version="1.0" encoding="utf-8"?> <selector ...

  4. pandas 必背函数操作

    1.五个常用属性 index,columns,shape,values,dtypes2.常用函数:set_index,reset_index,del df['column_name'],pd.read ...

  5. [R]dplyr及ggplot2中的变量引用列的问题

    问题描述: 存在这么一个场景,当需要动态选择列作为dplyr或ggplot2的输入时,列名的指定会出现问题. 以iris举例: # 以iris dataset为例 colnames <- c(& ...

  6. DWM1000 测距原理简单分析 之 SS-TWR代码分析1 -- [蓝点无限]

    蓝点DWM1000 模块已经打样测试完毕,有兴趣的可以申请购买了,更多信息参见 蓝点论坛 正文: 这一篇内容主要是通过官方源码理解SS-TWR 细节 代码下载链接:https://download.c ...

  7. jade模版js中接收express的res.render

    router: router.get('/', function(req, res, next) { res.render('index', { title:{name:'aaa',age:23} } ...

  8. MySQL错误码

    MySQL运行异常时,查看服务日志可看到error number,该错误码为系统调用出错码,具体如下. errno.00 is: Success    成功 errno.01 is: Operatio ...

  9. 前端性能优化 —— reflow(回流)和repaint(重绘)

    简要:整个在浏览器的渲染过程中(页面初始化,用户行为改变界面样式,动画改变界面样式等)reflow(回流)和repaint(重绘) 会大大影响web性能,尤其是手机页面.因此我们在页面设计的时候要尽量 ...

  10. C语言一个程序的存储空间

    按区域划分: 堆区:自动分配内存区.//堆栈段 栈区:手动分配内存区.//堆栈段 全局(静态)区:静态变量和全局变量.//数据段(读写) 常量区:存放const全局变量和字符串常量.//数据段(只读) ...