大家的阅读是我发帖的动力,本文首发于我的博客:deerblog.gu-nami.com/,欢迎大家来玩,转载请注明出处喵。

前言

bind是一个改变函数this指针指向的一个常用函数,经常用在涉及this指针的代码中。来看 MDN 的文档

Function实例的bind()方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其this关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。

最近搞出了一个很难注意得到的 bug,bind函数返回的函数中,原函数的属性消失了,导致了一个工具函数的失效。

function Message (/*...*/) {/*...*/}
Message.success = function (/*...*/) { return Message('success', /*...*/) }
// ...
xxx.Message = Message.bind(/*...*/)
// ...
xxx.Message.success(/*...*/)
// Uncaught TypeError: xxx.success is not a function

解决方法自然是Object.keys()遍历一下原函数的属性,添加到新的函数上面。

来看看文档怎么说的:

绑定函数还会继承目标函数的原型链。然而,它不会继承目标函数的其他自有属性(例如,如果目标函数是一个类,则不会继承其静态属性)。

所以上面Message的属性就消失了。

后来去翻看Funtion.__proto__.bind的文档发现了一些以前从未注意到的内容,感觉挺有意思...

bind的功能主要有两点,一个是修改函数的this指针。

例如在老版本的 React Class 组件中使用回调函数:

export default class TestButton extends Component {
testClickHandler () {
console.log(this.state)
}
render () {
return (
<Button onClick={this.testClickHandler.bind(this)}>test</Button>
)
}
}

setTimeout等回调中访问当前函数的this指针(当然现在我们都可以用箭头函数实现):

function test () {
var that = this
setTimeout(function () {
console.log(that.test)
})
}

bind还可以暂存参数:

const func = (a, b) => a + b
const funcBound = func.bind(null, 1)
funcBound(2) // 3
func(1, 2) // 3

我们还可以用这个特性做到函数柯里化,在复杂的场景中(好像业务开发几乎用不到的样子)使得函数调用更加灵活:

const curry = (func: Function, ...args: any[]) => {
let resArgsCount = func.length - args.length
let tempFunc = func.bind(null, ...args)
const ans = (...childArgs: any[]) => {
resArgsCount -= args.length
return resArgsCount > 0
? ((tempFunc = tempFunc.bind(null, ...childArgs)), ans)
: tempFunc(...childArgs)
}
return ans
} const test = (a, b, c) => a + b + c
const testCurry = curry(test, 1)
testCurry(2)
testCurry(3)
// 6

在 ES6 尚未普及的年代,我们并不能直接使用bind这个新特性,这就需要 polyfill,因此产生了很多相关的技巧(现在即使要兼容 IE 也可以直接通过 Bable 兼容),在 JS 中模拟实现bind经典面试题了属于是...

结合文档,这篇博客将在 JS 中实现一下bind的功能。

修改 this 指针和记录入参

众所周知,bind可以修改this的指向,并且记录入参:

function testFunc (a, b) {
return [a + b, this]
}
testFunc(1, 2)
// [3, Window] testFunc.bind({}, 1)(2)
//  [3, {…}]

下面就在 JS 中手写一下:

Function.prototype.deerBind = function(ctx, ...args) {
ctx = ctx || window
const self = this
return function (...argsNext) {
return self.apply(ctx, [...args, ...argsNext])
}
}
testFunc(1, 2)
// [3, Window] testFunc.deerBind({}, 1)(2)
//  [3, {…}]

作为构造函数

绑定函数自动适用于与 new 运算符一起使用,以用于构造目标函数创建的新实例。当使用绑定函数是用来构造一个值时,提供的this会被忽略。

在 JS 中,当你new一个对象时:

  1. 创建一个新对象;
  2. 构造函数this指向这个新对象;
  3. 执行构造函数中的代码;
  4. 返回新对象。

当一个函数被作为构造函数new的时候,它的this指向该函数的实例。这里我们修改一下deerBind函数的返回:

Function.prototype.deerBind = function(ctx, ...args) {
ctx = ctx || window
const self = this
const funcBound = function (...argsNext) {
return self.apply((this instanceof funcBound ? this : ctx), [...args, ...argsNext])
}
funcBound.prototype = self.prototype
return funcBound
} function testNew (str) { this.test = 'test ' + str }
new (testNew.bind({}, 'shikinoko nokonoko koshitanntann'))
// testNew {test: 'test shikinoko nokonoko koshitanntann'}
new (testNew.deerBind({}, 'shikinoko nokonoko koshitanntann'))
// testNew {test: 'test shikinoko nokonoko koshitanntann'}

另外,bind返回的函数的实例和原函数是指向同一个原型的,这里也满足了:

const ins1 = new (testNew.deerBind({}, 'test')), ins2 = new testNew('test')
ins1.__proto__
// {constructor: ƒ}
ins1.__proto__ === ins2.__proto__
// true

处理箭头函数的情况

注意到,箭头函数没有实例,也不能newthis来自亲代作用域,用作构造函数会引起错误:

new (() => {})
// Uncaught TypeError: (intermediate value) is not a constructor
new ((() => {}).bind())
// Uncaught TypeError: (intermediate value).bind(...) is not a constructor const testArrowFunc = (a, b) => {
return [a + b, this]
}
testArrowFunc(1, 2)
// [3, Window]
testArrowFunc.bind({}, 1)(2)
// [3, Window]

再修改一下这里的实现:

Function.prototype.deerBind = function(ctx, ...args) {
ctx = ctx || window
const self = this let funcBound
if (self.prototype) {
funcBound = function (...argsNext) {
return self.apply((this instanceof funcBound ? this : ctx), [...args, ...argsNext])
}
funcBound.prototype = self.prototype
} else {
funcBound = (...argsNext) => {
return self.apply(ctx, [...args, ...argsNext])
}
}
return funcBound
} testArrowFunc.deerBind({}, 1)(2)
// [3, Window]
new ((() => {}).deerBind())
// Uncaught TypeError: (intermediate value).deerBind(...) is not a constructor

处理类构造器的情况

你可能会发现,bind可以在类的构造器上面使用,但是我们上面自己写的似乎存在一点小错误:

class base { constructor (a, b) { this.test = a + b } }
new base(1, 2)
// base {test: 3}
new (base.bind({}, 1))(2)
// base {test: 3}
const bind = base.deerBind({}, 1)
new bind(2)
// Uncaught TypeError: Class constructor base cannot be invoked without 'new'

这里通过prototype上面的constructor来检查一个函数是否是构造器:

Function.prototype.deerBind = function(ctx, ...args) {
ctx = ctx || window
const self = this let funcBound
if (self.prototype) {
funcBound = function (...argsNext) {
return !self.prototype.constructor
? self.apply((this instanceof funcBound ? this : ctx), [...args, ...argsNext])
: new self(...args, ...argsNext)
}
funcBound.prototype = self.prototype
} else {
funcBound = (...argsNext) => {
return self.apply(ctx, [...args, ...argsNext])
}
}
return funcBound
} const bind = base.deerBind({}, 1)
new bind(2)
// base {test: 3}

我们实现的deerBind也可以用于构造函数了。

结语

到这里,bind大体就是实现完成了,这里具体涉及了bind函数改变this指针,记录参数以及作为构造函数的功能的实现。去看了一下著名 JS polyfill 库 core-js 的实现,感觉思路大概差不多的样子,它作为 polyfill 也考虑了兼容性的问题,感觉好厉害的样子。

参考:

  1. https://juejin.cn/post/6844903733013250056
  2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

细节解析 JavaScript 中 bind 函数的模拟实现的更多相关文章

  1. javascript中bind函数的作用

    javascript的bind的作用 <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...

  2. 博文推荐】Javascript中bind、call、apply函数用法

    [博文推荐]Javascript中bind.call.apply函数用法 2015-03-02 09:22 菜鸟浮出水 51CTO博客 字号:T | T 最近一直在用 js 写游戏服务器,我也接触 j ...

  3. 面试官:能解释一下javascript中bind、apply和call这三个函数的用法吗

    一.前言    不知道大家还记不记得前几篇的文章:<面试官:能解释一下javascript中的this吗> 那今天这篇文章虽然是介绍javascript中bind.apply和call函数 ...

  4. 浅析 JavaScript 中的 函数 currying 柯里化

    原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...

  5. 深入解析Javascript中this关键字的使用

    深入解析Javascript中面向对象编程中的this关键字 在Javascript中this关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如: function TestFun ...

  6. 【JavaScript】Javascript中的函数声明和函数表达式

    Javascript有很多有趣的用法,在Google Code Search里能找到不少,举一个例子: <script> ~function() { alert("hello, ...

  7. 深度解析javascript中的浅复制和深复制

    原文:深度解析javascript中的浅复制和深复制 在谈javascript的浅复制和深复制之前,我们有必要在来讨论下js的数据类型.我们都知道有Number,Boolean,String,Null ...

  8. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

  9. java基础62 JavaScript中的函数(网页知识)

    1.JavaScript中,函数的格式 function 函数名(形参列表){ 函数体; } 2.JavaScript中,函数需要注意的细节 1.在javaScript中,函数定义形参时,是不能使用v ...

  10. JavaScript中的函数(一)

    javaScript中的函数实际上是对象,每一个函数都是Function类型的实例,和其他引用类型一样具有属性和方法.由于函数是对象,因此函数名实际上也就是一个指向函数对象的指针,也就是函数对象的一个 ...

随机推荐

  1. 分享一个 Windows 下的透明锁屏工具【开源】

    透明锁屏 担心展示内容时被误操作打断? 害怕离开后忘记锁屏导致隐私泄露? 厌倦了千篇一律的系统锁屏界面? 透明锁屏 了解一下. 功能特点 告别误操作:锁屏状态下,屏幕内容依然可见,视频播放.PPT 演 ...

  2. vscode开发小程序2

    开发tab: 1.下载阿里图标到新建文件夹icons里面 2.在app.json里面的"windows"同层下设置tab: 默认样式的设置:小程序中不识别通配符*! 1. 2.查看 ...

  3. Kettle - 使用案例

    原文链接:https://blog.csdn.net/gdkyxy2013/article/details/117106691 案例一:把seaking的数据按id同步到seaking2,seakin ...

  4. 【BUUCTF】Easy Java

    [BUUCTF]Easy Java 题目来源 收录于:BUUCTF RoarCTF 2019 题目描述 经典登录框 不过SQL注入.目录扫描都没有发现 题解 点击页面的 help 跳转到/Downlo ...

  5. Week09_day05(Hbase的介绍和工作原理)

    HBase是一个分布式的.面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文"Bigtable:一个结构化数据的分布式存储系统".就像Bigtable利 ...

  6. Java、Python等接入方式对接股票数据源API接口

    为了创建一个Python项目来对接StockTV的API接口,我们可以使用requests库来发送HTTP请求,并使用websocket-client库来处理WebSocket连接.以下是一个简单的P ...

  7. 关于does not have a method xx to handle event "tap"我有话要说

    前言> 我正在对接微信小程序订阅消息功能,看了官方文档觉得挺简单的.于是踩坑开始了 ###### 应该是这样简单的```wx.requestSubscribeMessage({ tmplIds: ...

  8. 详解vue-router基本使用

    来源:https://m.jb51.net/article/111499.htm   本篇文章主要介绍了详解vue-router基本使用,详细的介绍了vue-router的概念和用法,有兴趣的可以了解 ...

  9. 寒武纪平台上传 Docker 镜像

    前言 学校的算力平台更换为了寒武纪平台,相较于以前简单的通过 Linux 用户隔离,使用门槛有所提升.但从整体来看,这样拥有更好的隔离性,在 docker 中即便搞崩了也可以重新来过,可以避免因他人的 ...

  10. elementui|dropdown|下拉菜单作为模态框使用

    elementui|dropdown|下拉菜单作为模态框使用 背景 场景:下拉菜单作为模态框使用: 操作:下拉菜单设置触发条件点击展示/隐藏:trigger="click" 目的: ...