当前位置: 首页 > news >正文

手动实现一个bind函数!

原文地址:手动实现一个bind函数! - 知乎

1.bind函数用法

bind()方法用于创建一个新的函数,这个新函数接收的第一个参数代表的就是this,利用bind()函数我就就可以任意改变函数内部的this指向了。

官网的解释:

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

官网解释得也比较通透明了,我们这儿为了让大家更加深刻理解bind的用法,利用代码来演示一下。

示例代码:

<script>let obj = {name: "小猪课堂",age: 20}// 声明一个函数function fn(a, b, c) {console.log("函数内部this指向:", this);console.log("参数列表:", a, b, c);}// 使用bind创建一个新函数let newFn = fn.bind(obj, 10, 20, 30);// 调用新函数newFn();// 调用旧函数fn(10, 20, 30);
</script>

输出结果:

上段代码中我们声明了一个函数fn,并且在函数内部打印了this以及参数,然后我们利用bind()创建了一个新的函数,且第一个参数传入了obj,意味着新函数内部的this指向了obj。分别执行两个函数,两个函数内部的this指向一个指向了全局,一个指向了window。

2.bind函数的特点

如果我们想要手动实现一个bind函数,那么非常有必要了解bind函数的特点,所以知己知彼方能百战不殆。

从上一节中的代码我们大致总结出了bind函数的以下几个特点:

2.1 返回一个新函数

bind函数实际上是对原函数的一个拷贝,原函数认可以按照原逻辑处理。

示例代码:

<script>let obj = {name: "小猪课堂",age: 20}// 声明一个函数function fn(a, b, c) {console.log("函数内部this指向:", this);console.log("参数列表:", a, b, c);}// 使用bind创建一个新函数let newFn = fn.bind(obj, 10, 20, 30);console.log(typeof newFn); // 'function'
</script>

2.2 新函数仍可继续传参

bind函数创建的新函数是可以接收参数的,之前的列子中我们是在创建的时候就将参数传递了进去,实际上可以不必传。

示例代码:

<script>let obj = {name: "小猪课堂",age: 20}// 声明一个函数function fn(a, b, c) {console.log("函数内部this指向:", this);console.log("参数列表:", a, b, c);}let newFn = fn.bind(obj, 10);newFn(20, 30);
</script>

输出结果:

上面的输出结果和我们直接在创建的时候传递所有参数得出的结果一致,而且上段代码中我们的参数是分开传递的,也就是说使用bind创建新函数后,调用新函数时,函数接收的参数是调用传入的参数+创建时传入的参数。

2.3 新函数作为构造函数

如果我们将使用bind创建的新函数当作构造函数来执行,那么this的指向将和bind创建时绑定的无关,它会指向一个新的引用。

示例代码:

<script>let obj = {name: "小猪课堂",age: 20}function fn(name) {this.name = name;console.log("函数内部this指向:", this);}let newFn = fn.bind(obj);let obj2 = new newFn("构造函数");
</script>

输出结果:

上段代码中我们使用bind新创建了一个函数newFn,而且将这个函数的this指向了obj,但是我们后续使用的时候使用了new关键词来创建,这个时候函数内部的this指向不在指向obj了,而是指向了fn。

那么既然this指向了fn,那么我们在fn原型上添加属性或方法后,obj2是能访问到的。

3.实现bind函数

既然我们知道了bind的几个特点,那么我们遵循它的即可特点就可以来实现它了。首先它是返回一个新函数,我们可以先把架子搭起来,代码如下:

Function.prototype.myBind = function () {// 返回新函数return function () {// 代码先省略}
}

上段代码只是一个基本的架子,我们在里面填充代码就好了。接下来我们需要将函数的this指向为传进来的第一个参数,并且使用bind创建的新函数可以继续接收参数,代码如下:

<script>let obj = {name: "小猪课堂",age: 20}// 手写bind函数Function.prototype.myBind = function (context) {const _this = this; // 当前函数let args = Array.from(arguments).slice(1); // 将参数列表转化为数组,出去第一个参数外// 返回新函数return function () {// context 是传进来的this_this.apply(context, args.concat(Array.from(arguments))); // 利用apply将this指向context,参数进行拼接}}// 声明一个函数function fn(a, b, c) {console.log("函数内部this指向:", this);console.log("参数列表:", a, b, c);}let newFn = fn.myBind(obj, 10, 20);newFn(30);
</script>

上段代码中需要注意的有两点,第一点是利用apply函数将函数的this指向了传经来的context,第二点是将参数新传进来的参数与args拼接,因为我们调用newFn时,可能传进来新参数,所以需要将新老参数拼接上。

输出结果:

上面的输出结果是和直接使用bind函数输出的结果是一样的。上面的代码还不够完善,如果我们将创建的新函数以构造函数的方式执行的话,this的执行和原生的bind不太一致。

代码如下:

<script>let obj = {name: "小猪课堂",age: 20}// 手写bind函数Function.prototype.myBind = function (context) {const _this = this; // 当前函数let args = Array.from(arguments).slice(1); // 将参数列表转化为数组,除去第一个参数外// 返回新函数return function () {// context 是传进来的this_this.apply(context, args.concat(Array.from(arguments))); // 利用apply将this指向context,参数进行拼接}}// 声明一个函数function fn(a, b, c) {console.log("函数内部this指向:", this);console.log("参数列表:", a, b, c);}let newFn = fn.myBind(obj, 10, 20); // 调用封装的bindlet newFn1 = fn.bind(obj, 10, 20); // 调用原生的bindnew newFn("myBind构造函数");new newFn1("bind构造函数");
</script>

输出结果:

上面的输出结果不一致,说明使用原生bind创建的新函数,如果使用构造函数的方式执行,那么函数内部的this执行会作为一个新的引用指向fn。

修改代码如下:

<script>let obj = {name: "小猪课堂",age: 20}// 手写bind函数Function.prototype.myBind = function (context) {const _this = this; // 当前函数let args = Array.from(arguments).slice(1); // 将参数列表转化为数组,除去第一个参数外// 返回新函数let fn = function () {// 如果被new调用,this应该是fn的实例return _this.apply(this instanceof fn ? this : (context || window), args.concat(Array.from(arguments)))}// 维护fn的原型let temp = function () { }temp.prototype = _this.prototype;fn.prototype = new temp(); // new的过程继承temp原型return fn};// 声明一个函数function fn(a, b, c) {console.log("函数内部this指向:", this);console.log("参数列表:", a, b, c);}let newFn = fn.myBind(obj, 10, 20);let newFn1 = fn.bind(obj, 10, 20)new newFn("myBind构造函数");new newFn1("bind构造函数");
</script>

输出结果:

上段代码的输出结果是不是就和实际的bind函数输出结果一样了啊!想要理解上段代码,大家有必要去学习以下JS中new一个对象发生了什么,主要是下面几步:

  • 创建一个新对象
  • 将构造函数的this赋值给新对象
  • 执行构造函数代码,给这个新的对象添加属性
  • 返回新的对象

具体的new实现过程还需要大家自己去理解。

总结

想要实现bind函数,就必须要理解其中的原理,无非就是改变this指向的问题。其中唯一的难点就是如何实现构造函数执行的方式,也就是要明白js中new一个对象的时候发生了什么?

相关文章:

手动实现一个bind函数!

原文地址&#xff1a;手动实现一个bind函数&#xff01; - 知乎 1.bind函数用法 bind()方法用于创建一个新的函数&#xff0c;这个新函数接收的第一个参数代表的就是this&#xff0c;利用bind()函数我就就可以任意改变函数内部的this指向了。 官网的解释&#xff1a; bind()…...

数据结构-时间复杂度/空间复杂度

Hello&#xff0c;好久没有更新了哦&#xff0c;已经开始学习数据结构了&#xff0c;这篇文章呢就是对刚学数据结构所接触到的时间复杂度进行一个分享哦&#xff0c;如果有错误之处&#xff0c;大家记得拍拍我哦~ 既然要讨论时间/空间复杂度&#xff0c;那我们就得知道时间/空…...

英语写作中“展示”、“表明”demonstrate、show、indicate、illustrate的用法

一、demonstrate、show、indicate在论文写作中主要用法是&#xff1a;demonstrate/show/indicate 从句&#xff1a; Sb./Sth. demonstrates/shows/indicates that ……从句中一般表达事实、观点和结论等。 例句&#xff1a; The authors demonstrated/showed/indicated that…...

Redis的java客户端

在Redis官网中提供了各种语言的客户端&#xff0c;地址&#xff1a;https://redis.io/resources/clients/ redis的java客户端 https://redis.io/resources/clients/#java 1.jedis使用 引入依赖 <dependency><groupId>redis.clients</groupId><artifac…...

Android环境配置笔记

文章目录 一、各环境文档二、参考 一、各环境文档 Gradle官方的兼容性文档&#xff1a;Java Compatibility 更新日期&#xff1a;2023.9.12 Android Gradle插件版本&#xff1a;Android Gradle Plugin 二、参考 参考文章&#xff1a;Android问题记录...

element-table 行的拖拽更改顺序(无需下载sortableJs

样例展示&#xff1a;vueelement 通过阅读element文档我们发现element并不提供拖拽相关的api 本博客通过element提供的行类名 注册函数 实现行与行的拖拽 1.设置el-table 的行样式类名 这里是用的是 function <el-table:data"outputData":row-class-name&qu…...

Docker部署jenkins

目录 一、jenkins原理二、Docker部署jenkins1.下载jenkins镜像文件2.查看下载的jenkins镜像3.创建Jenkins挂载目录并授权权限4.创建并启动Jenkins容器5.查看jenkins是否启动成功6.查看docker容器日志7.配置镜像加速8.访问Jenkins页面&#xff0c;输入ip地址加上9000端口9.获取管…...

从0到1学会Git(第三部分):Git的远程仓库链接与操作

写在前面:前面两篇文章我们已经学会了git如何在本地进行使用&#xff0c;这篇文章将讲解如何将本地的git仓库和云端的远程仓库链接起来并使用 为什么要使用远程仓库:因为我们需要拷贝我们的代码给别人以及进行协同开发&#xff0c;就需要有一个云端仓库进行代码的存储和同步&a…...

虚拟机Ubuntu操作系统常用终端命令(1)(详细解释+详细演示)

虚拟机Ubuntu操作系统常用终端命令 本篇讲述了Ubuntu操作系统常用的三个功能&#xff0c;即归档&#xff0c;软链接和用户管理方面的相关知识。希望能够得到大家的支持。 文章目录 虚拟机Ubuntu操作系统常用终端命令二、使用步骤1.归档1.1创建档案包1.2还原档案包1.3归档并压缩…...

redis实战-redis实现异步秒杀优化

秒杀优化-异步秒杀思路 未优化的思路 当用户发起请求&#xff0c;此时会请求nginx&#xff0c;nginx会访问到tomcat&#xff0c;而tomcat中的程序&#xff0c;会进行串行操作&#xff0c;分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一…...

Python爬虫-IP隐藏技术与代理爬取

前言 在进行爬虫程序开发和运行时&#xff0c;常常会遇到目标网站的反爬虫机制&#xff0c;最常见的就是IP封禁&#xff0c;这时需要使用IP隐藏技术和代理爬取。 一、IP隐藏技术 IP隐藏技术&#xff0c;即伪装IP地址&#xff0c;使得爬虫请求的IP地址不被目标网站识别为爬虫。…...

二刷力扣--链表

链表 链表类型&#xff1a; 单链表&#xff08;可以访问后面的一个节点&#xff09; 双链表&#xff08;可以访问前后节点&#xff09; 循环链表&#xff08;最后一个节点指向首节点&#xff09; 在Python中定义单链表节点&#xff1a; class ListNode:def __init__(self, v…...

返回值加const ,为了不拷贝得到成员的值,但被赋值的左值也要const

1. getA 函数返回值 什么都不加&#xff0c;也改不了c里面a的指针指向 why&#xff1f;返回成员变量时&#xff0c;会复制一下。 返回成员变量时&#xff0c;一般会赋值一下没有RVO_地摊书贩的博客-CSDN博客 2. getA 函数返回值 加了引用&#xff0c; 就没有复制 3. getA 函数…...

本地如何使用HTTPS进行调试

在现代前端开发中&#xff0c;HTTPS已经成为不可或缺的一部分&#xff0c;因为它在保护用户数据和确保网站安全性方面发挥着关键作用。然而&#xff0c;有时在本地开发过程中启用HTTPS可能会变得有些复杂。在本文中&#xff0c;我们将介绍如何轻松地在本地进行HTTPS调试&#x…...

观察者模式:对象之间的订阅机制

欢迎来到设计模式系列的第十三篇文章&#xff01;在之前的文章中&#xff0c;我们学习了许多常用的设计模式&#xff0c;今天我们将介绍观察者模式&#xff0c;它是一种行为型设计模式&#xff0c;用于定义对象之间的一对多依赖关系&#xff0c;当一个对象的状态发生变化时&…...

【1462. 课程表 IV】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 你总共需要上 numCourses 门课&#xff0c;课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite &#xff0c;其中 prerequisites[i] [ai, bi] 表示如果你想选 bi 课程&#xff0c;你…...

Kerberos 身份验证

简介 Kerberos 是一种由 MIT&#xff08;麻省理工大学&#xff09;提出的一种基于加密 Ticket 的身份认证协议。它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证&#xff0c;用于验证用户或主机的标识。。 适用范围&#xff1a;Windows Server 2022、Window…...

R语言贝叶斯METROPOLIS-HASTINGS GIBBS 吉布斯采样器估计变点指数分布分析泊松过程车站等待时间...

原文链接&#xff1a;http://tecdat.cn/?p26578 指数分布是泊松过程中事件之间时间的概率分布&#xff0c;因此它用于预测到下一个事件的等待时间&#xff0c;例如&#xff0c;您需要在公共汽车站等待的时间&#xff0c;直到下一班车到了&#xff08;点击文末“阅读原文”获取…...

通付盾入选2023年度“上市苗圃工程”重点企业

近日&#xff0c;2023年度苏州工业园区企业上市苗圃工程认定名单公示&#xff0c;江苏通付盾科技有限公司成功入选园区“上市苗圃工程”重点企业。 2023年第一批次苗圃企业认定结果&#xff1a; 企业上市苗圃工程 上市企业是衡量地方综合经济实力的重要标尺&#xff0c;也是区…...

SpringMVC之文件上传下载

SpringMVC是一个基于Java的Web框架&#xff0c;它提供了一套用于构建Web应用程序的开发模型。在SpringMVC中&#xff0c;文件上传和下载是常见的功能之一。 SpringMVC文件上传和下载的介绍&#xff1a; 介绍文件上传&#xff1a; 在SpringMVC中&#xff0c;文件上传功能可以通…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...

Modbus RTU与Modbus TCP详解指南

目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...