依据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? 为了大家遇到 ...
随机推荐
- 查看linux空间大小
du -sh : 查看当前目录总共占的容量.而不单独列出各子项占用的容量 du -lh --max-depth=1 : 查看当前目录下一级子文件和子目录占用的磁盘容量.
- CentOS 7 yum方式快速安装MongoDB
一.安装环境及配置yum # more /etc/redhat-release CentOS Linux release 7.2.1511 (Core) # vi /etc/yum.repos.d/m ...
- Selector-背景选择器
Selector-背景选择器 1.主要属性 <?xml version="1.0" encoding="utf-8"?> <selector ...
- pandas 必背函数操作
1.五个常用属性 index,columns,shape,values,dtypes2.常用函数:set_index,reset_index,del df['column_name'],pd.read ...
- [R]dplyr及ggplot2中的变量引用列的问题
问题描述: 存在这么一个场景,当需要动态选择列作为dplyr或ggplot2的输入时,列名的指定会出现问题. 以iris举例: # 以iris dataset为例 colnames <- c(& ...
- DWM1000 测距原理简单分析 之 SS-TWR代码分析1 -- [蓝点无限]
蓝点DWM1000 模块已经打样测试完毕,有兴趣的可以申请购买了,更多信息参见 蓝点论坛 正文: 这一篇内容主要是通过官方源码理解SS-TWR 细节 代码下载链接:https://download.c ...
- jade模版js中接收express的res.render
router: router.get('/', function(req, res, next) { res.render('index', { title:{name:'aaa',age:23} } ...
- MySQL错误码
MySQL运行异常时,查看服务日志可看到error number,该错误码为系统调用出错码,具体如下. errno.00 is: Success 成功 errno.01 is: Operatio ...
- 前端性能优化 —— reflow(回流)和repaint(重绘)
简要:整个在浏览器的渲染过程中(页面初始化,用户行为改变界面样式,动画改变界面样式等)reflow(回流)和repaint(重绘) 会大大影响web性能,尤其是手机页面.因此我们在页面设计的时候要尽量 ...
- C语言一个程序的存储空间
按区域划分: 堆区:自动分配内存区.//堆栈段 栈区:手动分配内存区.//堆栈段 全局(静态)区:静态变量和全局变量.//数据段(读写) 常量区:存放const全局变量和字符串常量.//数据段(只读) ...