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 852I Dating 树上莫队

    Dating 随便树上莫队搞一搞就好啦. #include<bits/stdc++.h> #define LL long long #define LD long double #defi ...

  2. Mysql5.7 单表 500万数据迁移到新表的快速实现方案

    开发过程中需要把一个已有500万条记录的表数据同步到另一个新表中,刚好体验下Mysql官方推荐的大数据迁移的方案:SELECT INTO OUTFILE,LOAD DATA INFILE Mysql ...

  3. Chapter 3 Introduction to Objects and Input/Output

    与声明一个primitive variable不同,声明一个对象的时候,并不创建用来存储一个对象的内存空间,而是创建了一个存储该对象所在内存空间的地址. 在java里,new是一个操作符,它让系统分配 ...

  4. 06-JDBC

    JDBC > JAVA Database Connectivity java 数据库连接 * 为什么会出现JDBC > SUN公司提供的一种数据库访问规则.规范, 由于数据库种类较多,并且 ...

  5. day9面向对象,多线程2

    一.获取全部qq成员头像 import os,requests,pymongo class Qqgroup(object):#定义一个类 #定义获取QQ群的请求rul,抓包获取 group_rul = ...

  6. vue_eHungry 饿了么

    eHungry 仿饿了么 git 操作 git checkout -b dev        // 创建新分支 dev git push origin dev        // 代码推送到 dev ...

  7. Eureka源码解读

    Eureka是我接触分布式软件和服务的第一个框架,所以其原理和实现我的好好研究一下,Eureka可以参看这篇博文:http://springcloud.cn/view/29 初学者会在教程中看到使用@ ...

  8. JavaScript继承的几种模式

    原型链 让一个类的原型对象指向另一个类的实例

  9. iOS HTML特殊字符转译

    something about you Bye Bye è; un saluto ​ 我使用NSXMLParser,它是顺利,直到它找到èHTML实体.它要求foundCharacters:为“再见” ...

  10. lamdba表达式

    lambda表达式是一个可传递的代码块,可以在以后执行一次或多次. lambda表达式的语法: 1. 参数 -> 表达式(无需指定返回类型) (String first, String seco ...