• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
千無
落雨听书,叹红尘……
博客园    首页    新随笔    联系   管理    订阅  订阅

依据ECMA规范,手写一个bind函数

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

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

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

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

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

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

function FunctionIsConstructor(fnc) {
  let isConstructor = true;
  try {
    Object instanceof fnc
  } catch (e) {
    if (e instanceof TypeError) {
      isConstructor = false
    }
  }
  return isConstructor
}
function BoundFunctionCreate(targetFunction, boundThis, boundArgs) {
  let proto = Object.getPrototypeOf(targetFunction);
  let boundFunction = function () {
    if (new.target) {
      // 实现构造函数功能
      if (FunctionIsConstructor(targetFunction)) {
        return new targetFunction(...boundArgs)
      } else {
        throw new TypeError(`${arguments.callee.name} is not a constructor`)
      }
    } else {
      // 实现函数调用功能
      return targetFunction.call(boundThis, [...boundArgs, ...arguments])
    }
  }
  delete boundFunction.name;
  Object.setPrototypeOf(boundFunction, proto)
  return boundFunction;
}
function isCallable(Target) {
  if (typeof Target === 'function') return true;
  return false;
}
function ToInteger(arg) {
  let number = Number(arg);
  if (number !== number) return +0;
  if (number === 0 || number === Infinity || number === -Infinity) return number;
  return Math.floor(Math.abs(number));
}
function SetFunctionName(F, name, prefix) {
  if (typeof name === 'symbol') {
    let description = name.description
    if (description === undefined) {
      name = ''
    } else {
      name = `[${description}]`
    }
  }
  if (prefix) {
    name = `${prefix} ${name}`
  }
  return Object.defineProperty(F, 'name', {
    value: name,
    writable: false,
    enumerable: false,
    configurable: true
  })
}
function SetFunctionLength(F, Target, args) {
  let targetHasLength = Target.hasOwnProperty('length');
  let L;
  if (targetHasLength) {
    let targetLen = Target.length;
    if (typeof targetLen !== 'number') {
      L = 0;
    } else {
      targetLen = ToInteger(targetLen)
      L = Math.max(0, targetLen - args.length)
    }
  } else {
    L = 0;
  }
  Object.defineProperty(F, 'length', {
    value: L,
    writable: false,
    enumerable: false,
    configurable: true
  })
}

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

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

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

最后,简单测试一下。

var modules = {
  x: 42,
  getX: function() {
    console.log('this', this === modules, this.x)
    return this.x;
  }
}

var unboundGetX = modules.getX;
console.log('unbounnd ', unboundGetX()); // The function gets invoked at the global scope
// // expected output: unbounnd undefined

var boundGetX = boundFuntion(unboundGetX, modules);
console.log('bounnd ', boundGetX());
// expected output: bounnd 42

总结

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

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

posted @ 2019-03-22 18:48  千無  阅读(287)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3