手动实现一个bind函数!

news2024/11/25 0:26:44
原文地址:手动实现一个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); // 调用封装的bind
  let newFn1 = fn.bind(obj, 10, 20); // 调用原生的bind


  new 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一个对象的时候发生了什么?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

缩小检索范围、精准检索文献的方法

搜索文献也需要找到正确的方法&#xff0c;因为不正确的搜索方法直接影响搜索结果。要么发现的文献与我们需要的无关&#xff0c;要么检索到的文献很少&#xff0c;这不仅浪费时间&#xff0c;而且浪费精力。本文整理了准确检索文献、缩小文献检索范围的详细方法&#xff0c;希…

三步高效搭建在线帮助中心,这几个技巧与工具必须掌握

搭建一个高效的在线帮助中心是提供优质客户支持的重要组成部分。下面将介绍三个步骤&#xff0c;以及必须掌握的技巧和工具&#xff0c;帮助你快速搭建一个高效的在线帮助中心。 第一步&#xff1a;规划和准备 在搭建在线帮助中心之前&#xff0c;首先需要进行一些规划和准备…

python自动生成小学四则运算题目

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 题目一 1.题目要求&#xff1a; 设计一个程序&#xff0c;帮助小学生练习10以内的加法 详情: 随机生成加法题目; 学生…

《向量数据库指南》——向量数据库会是 AI 的“iPhone 时刻”吗?

最近一年,以 ChatGPT、LLaMA 为代表的大语言模型的兴起,将向量数据库的发展推向了新的高度。 向量数据库是一种在机器学习和人工智能领域日益流行的新型数据库,它能够帮助支持基于神经网络而不是关键字的新型搜索引擎。向量数据库不同于传统的关系型数据库,例如 PostgreSQ…

Vue3 Element-Plus 主题切换方案

1. .html 文件中&#xff0c;设置 <html> 标签的 “data-theme” 属性 2. 单独创建主题的样式文件 .css/.scss &#xff0c;并导入 3. 样式文件中创建不同主题对象 4. 定义不同主题中的样式变量 注意&#xff1a;左右两个主题的变量名一样&#xff0c;值不同 5. 页面样式…

【Hive SQL】统计同名路径下目录数量(基于reverse、split和substr函数)

首先&#xff0c;Hive事务表所产生的的路径信息如下&#xff1a; PS&#xff1a;其中路径信息格式为 /user/hive/warehouse/${database_name}.db/${table_name}/*/user/hive/warehouse/test.db/tran_ts/delete_delta_0000002_0000002_0000 /user/hive/warehouse/test.db/tran_…

VUE响应式

响应式 :::tip 提示 我们了解过响应式可以同步更新数据和视图&#xff0c;但是其工作原理我们最好也要了解一下。这样当你使用时遇到一些常见的错误&#xff0c;也能够快速定位是什么问题导致的。 了解响应式原理之前&#xff0c;你必须要先去了解 ES5 的 Object.defineProper…

项目管理的工作分解结构 (WBS):如何创建及使用

工作分解结构 (WBS) 是一种可视化的项目分解。通过将项目分解为更小的组件&#xff0c;WBS 可以将范围、成本和可交付成果集成到一个工具中。虽然大多数 WBS 是基于可交付成果的&#xff0c;但它们也可以是基于阶段的。下面来详细了解 WBS 可以为项目管理做些什么。 项目管理中…

Linux 磁盘管理,分区,文件系统

目录 磁盘基本概念 磁盘在Linux中的表示 分区的概念&#xff0c;分区是软件概念 MBR GPT 使用fdisk进行磁盘管理 文件系统 mke2fs mkfs mke2fs mkfs dumpe2fs 日志 e2label fsck (file system check) 挂载、使用文件系统&#xff0c;mount,unmount 自动挂载…

大数据平台测试

简介&#xff1a; 一.大数据平台测试简述 大数据平台测试包括2部分&#xff1a;基础能力测试和性能测试 Ⅰ).基础能力测试 大数据平台的基本功能和数据的导入导出对SQL任务、NoSQL任务、机器学习、批处理任务的支持 大数据平台是否能够通过界面的形式方便用户进行非运行维护&am…

PCIE2.0/PCIE3.0/PCIE4.0/PCIE5.0接口的带宽、速率计算,nvlink互联

PCIE2.0/PCIE3.0/PCIE4.0/PCIE5.0接口的带宽、速率计算 https://blog.csdn.net/weixin_42229404/article/details/84069859 PCI-Express(peripheral component interconnect express)是一种高速串行计算机扩展总线标准&#xff0c;它原来的名称为“3GIO”&#xff0c;是由英特…

虚拟机突然无法访问外部网络的现象集合

现场还原 虚拟机突然无法访问外部网络 ping 8.8.8.8的时候显示网络不可达 ping 8.8.8.8ping www.baidu.com&#xff08;报&#xff1a;未知的名称或服务或请求超时&#xff09; ping www.baidu.comyum操作 也提示错误&#xff1a;为仓库 ‘appstream’ 下载元数据失败 : C…

device or resource busy

最近要删除ubuntu下面的某个文件的时候&#xff0c;突然报错: "device or resource busy", 于是通过 如下命令: lsof | grep /projects/m/CMLR_processed_codeformer_HD/20110330/02313/.nfs0000000001dedb1b00000003 发现是 5953号进程占用了&#xff0c;于是kill…

win11鼠标指针怎么设置

win11有很多自定义的功能&#xff0c;这个功能可以让我们根据自己的喜好进行系统的自定义设置&#xff0c;很多小伙伴在将电脑更新成win11之后不知道该怎么设置鼠标指针&#xff0c;这里小编就给大家带来win11鼠标指针的设置方法&#xff0c;还不太清楚的小伙伴快来跟小编一起了…

SpringMvc第五战-【SpringMvcJSR303和拦截器】

前言&#xff1a; 小编阐述了springmvc 中的文件下载&#xff0c;以及jrebel的使用和文件下载以及多文件下载! 在本次小编将会介绍JSR303的概念&#xff0c;应用场景和在具体实例的使用&#xff1b;和拦截器的应用 一.JSR303的介绍 1.什么是JSR303&#xff1f; JSR是Java S…

清理不干净等于白清理!如何擦除所有个人数据并擦除iPhone和iPad

我们大多数人每天都在使用iPhone和iPad。在设备的使用寿命中&#xff0c;我们会积累大量敏感数据和信息&#xff0c;包括消息、照片、视频、联系人、信用卡、密码等等。这就是为什么确保你有所有重要物品的备份很重要&#xff0c;这样你就可以在卖掉或交给别人之前把它扔掉。 …

13.6 Production State Awareness (PSA)

1. Introduction UFS设备可以利用有关其生产状态的知识&#xff0c;相应地调整内部操作。 例如&#xff0c;在设备焊接之前加载到存储设备中的内容可能被破坏&#xff0c;其概率高于regular模式。UFS设备可以在设备焊接前使用“Special”内部操作加载内容&#xff0c;这将减少…

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

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

04JVM_语法糖

一、编译期处理 语法糖java编译器把*.java源码编译为*.class字节码的过程中&#xff0c;自动生成和转换的一些代码&#xff08;添加的class字节码&#xff09;&#xff0c;减轻程序员的负担。 1.默认构造器 默认构造器没有写任何的构造方法&#xff0c;但经过编译器编译成字…

pdfjs解决ie浏览器预览pdf问题

pdfjs是一个js库&#xff0c;可以将pdf文件用canvas重新绘制&#xff0c;从而无需借助pdf读取插件就可以直接预览。 目前chrome内核的浏览器已内置pdf读取插件&#xff0c;但ie浏览器还没有。而我们最近在做的一个项目使用对象是医院&#xff0c;使用的浏览器竟然还是ie。所以我…