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函数的更多相关文章

  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. Codeforces 628F 最大流转最小割

    感觉和昨天写了的题一模一样... 这种题也能用hall定理取check, 感觉更最小割差不多. #include<bits/stdc++.h> #define LL long long # ...

  2. 洛古P1036 选数 题解

    [我是传送门] 这是一道很经典的深搜与回溯(难度一般) 可是就这个"普及-" 让本蒟蒻做了一晚上+半个上午(实际我不会深搜回溯,全靠框架+去重); 下面让我分享下本蒟蒻的(全排列+ ...

  3. C#堆和栈

    一.在讲堆栈之前,我们先看看值类型和引用类型: 1,我们看看值类型与引用类型的存储方式: 引用类型:引用类型存储在堆中.类型实例化的时候,会在堆中开辟一部分空间存储类的实例.类对象的引用还是存储在栈中 ...

  4. Egret--添加一个精灵事件

    class Hello extends egret.DisplayObjectContainer{ //入口函数 private onAddStage(event:egret.Event){ //打开 ...

  5. __x__(30)0908第五天__导航条的练习 <div>版本

    效果图:  html源代码: <!doctype html> <html> <head> <meta charset="utf-8" /& ...

  6. [Codeforces Round #438][Codeforces 868C. Qualification Rounds]

    题目链接:868C - Qualification Rounds 题目大意:有\(n\)个题目,\(k\)个人,每个人可能做过这\(n\)个题里的若干道,出题方要在这\(n\)个题目里选若干个出来作为 ...

  7. ElasticSearch 一

    一:elasticSearch的目录结构的分类 /bin 运行ElasticSearch实例和管理插件呢的一些脚本 /config 配置文件 路径,包含elasticsearch.yml /data ...

  8. maven jdk版本

    http://maven.apache.org/docs/history.html Maven Releases History Date format is: YYYY-MM-DD Maven 3 ...

  9. 4.29python

    题目: 代码:(?) list = input().split()list1 = []list2 = []for i in range(len(list)): if (i+1)%3 != 0 and ...

  10. Pycharm汉化

    简单的汉化方法:    需要把以下链接的文件放到Pycharm安装目录的lib目录下记得重启pycharm即可https://pan.baidu.com/s/1GG4zXZ_0xSB-AlDdu0Tu ...