依据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? 为了大家遇到 ...
随机推荐
- github使用的小坑 处理
1.本地版本 低于线上版本,并做了修改 ,线上版本也做了修改的情况下,提交内容,必须解决冲突 会出现 解决方法 冲突的几个文件 进行初始还原 在进行 更新 与线上同步,在将需要提交的/新增/修改的内容 ...
- Codeforces 715B. Complete The Graph 最短路,Dijkstra,构造
原文链接https://www.cnblogs.com/zhouzhendong/p/CF715B.html 题解 接下来说的“边”都指代“边权未知的边”. 将所有边都设为 L+1,如果dis(S,T ...
- Java 常用知识点
Java 常用知识点 1.日期格式化 SimpleDateFormat Date date=new Date(System.currentTimeMillis()) ; SimpleDateForma ...
- DWM1000 多个基站定位讨论 --[蓝点无限]
该篇是之前<DWM1000 多个标签定位讨论 --[蓝点无限]>的续篇 多基站定位也是定位必然,因为有些稍微大一点的场合,或者多个区域(厂区不同房间)定位,往往4个基站会严重不足. DWM ...
- 使用python处理地理数据:Geopandas
说句实话处理地理数据使用python还是比较麻烦的
- saprfc
PHP在使用saprfc的时候,首先需要安装 saprfc 拓展,然后在引入saprfc.php类库,最后在使用. 一.PHP saprfc拓展的安装(Linux): 安装方法: 安装时需 ...
- __x__(40)0909第五天__表格 table 的 css 样式 美化
如果就向下面的代码那样,不写 tbody , 则浏览器自添加 tbody , 并将所有的 tr 移入 tbody 意味着 tr 并非 table 的子元素,而是 tbody 的子元素. 所以 以后编写 ...
- 关于*.ashx 处理程序调试时 未能创建类型 错误
出现改问题的根本原因是因为,我更改过改类型的名字,而IDE并没有更改 ***.ashx. 注册类型名字. 所更改的类的名字应该是 "项目名字.文件名字“ 例如下图 应该更改类为 ”Wx_ ...
- 简单的SQL查询,循环插入
- Retrieve pictures from Excel file using OLEDB
string file = @"C:\sample.xlsx"; if(System.IO.File.Exists(file)) { Microsoft.Office.Intero ...