大家的阅读是我发帖的动力,本文首发于我的博客: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. flutter-使用flutter_app_upgrade进行版本更新要点

  2. 动手学深度学习-python基础知识介绍part1

    基础详解-part1 import torch x=torch.arange(12) x x.shape x.numel() #数组中元素的总数 # 修改形状 x.reshape(3,4) torch ...

  3. WPF 线程处理

    参考链接:https://docs.microsoft.com/zh-cn/previous-versions/dotnet/netframework-3.5/ms771750(v=vs.90)

  4. 【数学公式】mathtype和word2016集成

    mathtype 安装好了以后,word 没有相应的选项卡怎么办? 问题 解决办法 找到word的启动路径 2. 找到mathtype 安装好后的mathpage文件夹 进入文件夹,找到MathPag ...

  5. google浏览器删除token

    测试登录时长,页面是否返回到首页 删除token

  6. SSH登录:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

    错误信息: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICAT ...

  7. 解决Oracle锁表情况

    在使用Oracle数据库更新数据的时候,有两种查询方式可以修改编辑数据: select t.*,t.rowid from table t select * from table for update ...

  8. ORB算法介绍 Introduction to ORB (Oriented FAST and Rotated BRIEF)

    Introduction to ORB (Oriented FAST and Rotated BRIEF) 1. Introduction ORB(Oriented FAST and Rotated ...

  9. BUUCTF---达芬奇的密码

    题目 达芬奇隐藏在蒙娜丽莎中的数字列:1 233 3 2584 1346269 144 5 196418 21 1597 610 377 10946 89 514229 987 8 55 6765 2 ...

  10. 在Java集合框架中,`Set`接口是一个重要的接口,它表示一个不包含重复元素的集合。常见的`Set`实现类有`HashSet`、`LinkedHashSet`和`TreeSet`。下面是关于`Set`接口的一些基本用法和方法介绍:

    常用实现类 HashSet: 基于哈希表实现,元素无序. 插入.删除.查找操作的时间复杂度为O(1). LinkedHashSet: 继承自HashSet,并使用双向链表来维护元素的插入顺序. 保留元 ...