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. Vue H5 项目模板

    使用了 mint-ui sass vue fastclick vue router 一个项目的初始化状态,一个新项目,陆陆续续花了2天时间搭起来的. 里面有mint-ui的基本用法 tabbar 还有 ...

  2. redis初步入门(1)

    一.redis是一款高性能NOSQL系列的非关系型的数据库,其是用C语言开发的一个开源高性能键值对(key-value)数据库. 二.redis的应用场景 1.缓存(数据查询.短连接.新闻内容.商品内 ...

  3. vscode断点调试工程化服务端文件

    一.创建express应用我们使用express-generator创建一个新的express应用.1.全局安装express-generator // 安装 sudo npm install exp ...

  4. Django聚合分组查询、常用字段

    首先回顾sql中聚合和分组的概念: 如果没有分组,会把整张表作为一个大组,查询字段必须是聚合结果:如果有分组,分组之后,必须要使用聚合的结果作为having的条件. 聚合查询 聚合:aggregate ...

  5. MVC:添加Html辅助器

    本文的方法来自 <精通asp.net Mvc5>. 一.添加视图模型 为了支持Html辅助器方法,可以把可用页面数,当前面,以及存储库中产品数等信息传递给视图.在Model文件夹内增加一个 ...

  6. tp5 查询单个字段的值

    $num_lastday = Db::name('test_wx') ->where('num','=',$data['num']) ->order('time desc') ->l ...

  7. 170217、nginx 安装时候报错:make: *** No rule to make target `build', needed by `default'. Stop.

    出现此种情况,是linux系统没有安装先决条件 1.GCC——GNU编译器集合(GCC可以使用默认包管理器的仓库(repositories)来安装,包管理器的选择依赖于你使用的Linux发布版本,包管 ...

  8. [No000019A]【波浪理论精典教学课程】

    波浪理论的产生和发展     拉尔夫·纳尔逊·艾略特(Ralph Nelson Elliott ),是波浪理论的创始人.1871年7月28日出生在美国密苏里州堪萨斯市的玛丽斯维利镇Marysville ...

  9. 报文分析6、ARP报头结构

    ARP报头结构   硬件类型 协议类型 硬件地址长度 协议长度 操作类型 发送方的硬件地址(0-3字节) 源物理地址(4-5字节) 源IP地址(0-1字节) 源IP地址(2-3字节) 目标硬件地址(0 ...

  10. Vue.js的简介

    vue.js简介 Vue.js读音 /vjuː/, 类似于 view   Vue.js是前端三大新框架:Angular.js.React.js.Vue.js之一,Vue.js目前的使用和关注程度在三大 ...