当前位置: 首页 > 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;文件上传功能可以通…...

多自由度冗余空间机械臂位姿一体化规划与控制【附代码】

✨ 长期致力于空间机械臂、对偶四元数、位姿一体化、路径规划、跟踪控制研究工作&#xff0c;擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流&#xff0c;点击《获取方式》 &#xff08;1&#xff09;基于对偶四元数的冗余机械臂运…...

Python开发者首次使用Taotoken接入大模型API的完整步骤指南

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Python开发者首次使用Taotoken接入大模型API的完整步骤指南 对于Python开发者而言&#xff0c;接入大模型API进行应用开发已成为一…...

环境光遮蔽(Ambient Occlusion):揭秘那个让虚拟世界“有重量感“的阴影魔法

一、一个让我"开窍"的老木匠故事 我有个朋友是传统家具的修复师&#xff0c;他给我讲过一个让我至今难忘的故事。他说他刚入行时跟着一位 70 多岁的老木匠师父学习——师父让他做的第一件事不是雕花、不是榫卯——而是"看阴影"——这个看似奇怪的训练改变了…...

苏州创新药20年,站上全球产业洗牌暴风眼

一个城市的创新药产业集群如何从无到有&#xff0c;又如何在全球化临界点寻找自己的位置。文&#xff5c;徐鑫编&#xff5c;任晓渔过去一年多&#xff0c;苏州是全球创新药产业版图中一个绕不过去的城市。大额海外授权交易频繁传出&#xff0c;在中国高端制造走出去的背景下&a…...

Transient、QuickEye、VerifyEye傻傻分不清?一文讲透Ansys里三种眼图仿真方法的适用场景与避坑指南

Transient、QuickEye、VerifyEye深度解析&#xff1a;Ansys眼图仿真技术选型实战指南 在高速数字系统设计中&#xff0c;眼图分析是评估信号完整性的黄金标准。面对Ansys工具链中三种截然不同的眼图生成方法&#xff0c;工程师常常陷入选择困境——是追求精确度的传统瞬态分析&…...

终极免费方案:WandEnhancer完整解锁WeMod Pro功能快速指南

终极免费方案&#xff1a;WandEnhancer完整解锁WeMod Pro功能快速指南 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/gh_mirrors/we/Wand-Enhancer 你是否渴望享受WeMod Pro会员的所…...

搞定这 5 个全栈电商项目,面试别再用 Todo-List 凑数了

找独立开发练手项目或者写简历项目时&#xff0c;最忌讳两件事&#xff1a;一是太简单&#xff08;纯前端 Mock 数据&#xff0c;点两下就没了&#xff09;&#xff0c;二是太假&#xff08;一上来就硬套微服务、消息队列、高并发&#xff0c;结果自己根本Hold不住&#xff09;…...

使用TaotokenCLI工具一键配置开发环境中的API密钥

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 使用Taotoken CLI工具一键配置开发环境中的API密钥 在团队协作或个人开发中&#xff0c;为每个项目或成员手动配置大模型API密钥和…...

基于MAX78000的医疗紧急呼叫系统:边缘AI与低功耗设计实战

1. 项目概述与核心价值大家好&#xff0c;我是Victor Hugo&#xff0c;一名电子工程师。今天我想和大家分享一个我最近完成并参与设计竞赛的项目&#xff1a;一个基于MAX78000 FTHR开发板的医疗紧急呼叫辅助系统。这个项目的核心&#xff0c;不是从零开始造一个新轮子&#xff…...

PCB的常规机械通孔与HDI工艺钻孔差异

结合常规 4 层通孔 PCB&#xff08;非 HDI&#xff09; 标准制程&#xff0c;分步骤讲清钻孔时机、先后顺序&#xff0c;区分机械通孔与板件结构&#xff0c;专业且贴合工厂实际流程。一、先明确 4 层通孔板基础结构4 层板结构&#xff1a;L1 → PP 半固化片 → L2/L3&#xff…...