依据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调用当前函数,也是值得去了解的。
依据ECMA规范,手写一个bind函数的更多相关文章
- 手写Function.bind函数
if(!Function.prototype.bind){ Function.prototype.bind = function(oThis){ if(typeof this !=="fun ...
- 手写一个bind
1 Function.prototype.bind1 = function(){ 2 // 将类数组转化成数组 3 let arr = Array.prototype.slice.call(argum ...
- 手写简化版printf函数
2019.02.01更新:经同学提醒,myprintf函数应有返回值为输出的字符数. 期末的大作业,手写一个myprintf函数,支持如下一些操作. 也就是 % -(负号控制左右对齐) 数(控制字段 ...
- 只会用就out了,手写一个符合规范的Promise
Promise是什么 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可以获取异步操作的消息.Prom ...
- 剖析手写Vue,你也可以手写一个MVVM框架
剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...
- 『练手』手写一个独立Json算法 JsonHelper
背景: > 一直使用 Newtonsoft.Json.dll 也算挺稳定的. > 但这个框架也挺闹心的: > 1.影响编译失败:https://www.cnblogs.com/zih ...
- 手写事件代理函数 (Delegated function)
‘手写 ’ 这个词 ,面试是不是听过无数遍呢 ! 今天我们来手写一个这样的事件委托函数 => function( parent, selector, type , handle) {} 你需 ...
- 看年薪50W的架构师如何手写一个SpringMVC框架
前言 做 Java Web 开发的你,一定听说过SpringMVC的大名,作为现在运用最广泛的Java框架,它到目前为止依然保持着强大的活力和广泛的用户群. 本文介绍如何用eclipse一步一步搭建S ...
- 用过消息队列?Kafka?能否手写一个消息队列?懵
是否有同样的经历?面试官问你做过啥项目,我一顿胡侃,项目利用到了消息队列,kafka,rocketMQ等等. 好的,那请开始你的表演,面试官递过一支笔:给我手写一个消息队列!!WHAT? 为了大家遇到 ...
随机推荐
- sql防止注入
使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入.在使用参数化查询的情况下,数据库系统不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才 ...
- window编程_消息分类
Window应用程序利用Windows消息(Message)与应用程序及系统进行信息交换.消息分为:消息号:有事先定义好的消息名标识字节参数(wParam):用于提供消息的附加信息长字节参数(lPar ...
- bzoj5397 circular 随机化(
题目大意 给定一个环,环上有一些线段,试选出最多的线段 题解: 提醒:这可能是一篇非常欢乐的题解 我们考虑倍长环,然后断环为链 我们考虑枚举开头的线段,然后做一次贪心 这样子的复杂度根据实现的不同是\ ...
- 程序一 用记事本建立文件src.dat,其中存放若干字符。编写程序,从文件src.dat中读取数据,统计其中的大写字母、小写字母、数字、其它字符的个数,并将这些数据写入到文件test.dat中。
用记事本建立文件src.dat,其中存放若干字符.编写程序,从文件src.dat中读取数据,统计其中的大写字母.小写字母.数字.其它字符的个数,并将这些数据写入到文件test.dat中. #inclu ...
- SDOI2018:荣誉称号
题解: https://files.cnblogs.com/files/clrs97/title-solution.pdf Code: #include<cstdio> #include& ...
- vue_组件间通信:自定义事件、消息发布与订阅、槽
自定义事件 只能用于 子组件 向 父组件 发送数据 可以取代函数类型的 props 在父组件: 给子组件@add-todo-event="addTodo" 在子组件: 相关方法中, ...
- codecademy课程笔记——JavaScript Promise
Promise是一种表示异步操作最终的结果的对象,一个Promise对象有三种状态 Pending: 初始状态 ,操作还未完成 Fullfilled:操作成功完成,且这个promise现在有一个r ...
- java 实现文件上传下载以及查看
项目的目录结构 代码 IOUtils.java package cn.edu.zyt.util; import java.io.IOException; import java.io.InputSt ...
- 白盒测试实践-day01
一.任务进展情况 了解本次测试的主要任务,进行小组分工. 二.存在的问题 由于最近比较忙,任务来不及做. 三.解决方法 坚持一下.
- Nginx+Https自己敲命令生成证书
nginx配置https访问 一.准备 环境:centos6.8 nginx:1.13.6 二.开始 首先安装依赖包: yum install -y gcc gcc-c++ autocon ...