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

手写实现call() apply() bind()函数,附有详细注释,包含this指向、arguments讲解

手写实现call() apply() bind()函数是很经典的问题,但是能掰扯清楚的文章确实不算多,于是笔者才决定写下本文,希望能给读者带来一些启发,如有错误欢迎指正。

目录

补充知识

函数中的this指向

 类数组对象arguments

call()

原理:

详细实现:

验证:

apply()

原理:

详细实现:

 验证:

bind()

原理:

详细实现:

验证:


补充知识

在往下看具体的实现之前,我们先要了解一些前置补充知识

函数中的this指向

函数中的this指向是在函数被调用的时候确定的,也就是执行上下文被创建时确定的。

在一个执行上下文中,this由调用者提供,调用函数的方式来决定。

1.方法调用模式

哪个对象调用函数(object.method()),this就指向哪个对象

let obj={a:1,b:2,
}
obj.test=function(){let a=3console.log(this,this.a)
}
obj.test()
//结果为 { a: 1, b: 2, test: [Function: test] } 1
//test的this指向obj

2.独立调用模式

独立调用时,指向window(严格模式下指向undefined)

// 例子1
window.a=111
var test1 =function(){let a=123console.log(this.a)
}
test1() // 输出结果为111,说明此时test1的this指向是全局的window// 例子2
window.a=111
var obj = {a:222
}obj.test2=function(){let a=333;console.log(this.a,'第一个')let func=function(){console.log(this.a,'第二个')}func()
}obj.test2()
// 输出结果为:
// ’222 第一个‘ 说明this指向是obj,因为test2是方法调用
// ‘111 第二个’ 说明this指向是window,因为func是独立调用

3.构造函数模式

js中,我们通过new关键词来调用构造函数,此时this会绑定在该实例对象

window.a=111
test3=function(){this.a=444
}
test3()
console.log(window.a) 
//结果为‘444’,说明this指向的是windows,因为上面test3的调用模式是独立调用let newObj= new test3()
console.log(newObj.a) 
// 结果为‘444’,说明this指向的是newObj,因为上面的test3的调用是作为构造函数的形式调用

 类数组对象arguments

arguments只在函数中存在(箭头函数除外)的伪数组(具有length,可以通过下标访问,但是不具有数组的方法, 比如push(),pop()等数组常用的方法),存储了我们传入的所有形参

例子

function testArgu(){console.log(arguments);
}
testArgu('a','b',123)

运行结果

我们可以看到,我们传的参数都在arguments中了 

call()

原理

当函数执行call方法的时候,实际上是把函数放到call()的第一个参数的某个属性上,然后再通过合格属性来执行函数

func.call(ctx,arg1,...)//等价于以下代码
ctx.fn=func;
ctx.fn(arg1,....)

详细实现

Function.prototype.myCall=function(context,...args){//对this进行类型判断,如果不是function类型,就报错//this应该指向的是调用myCall函数的对象(function也属于对象object类型)//因为myCall的调用模式是上文提到的‘方法调用模式’if(typeof this != 'function'){throw new TypeError('type error')}// 不传的话,默认上下文是windowvar context = context || window// 假如context上面有fn属性的时候,会出现冲突// 所以当context上面有fn属性的时候,要先保存一下var temp = nullif (context.fn) {temp = context.fn}// 给context创建一个fn属性,并将值设置为需要调用的函数(即this)context.fn = this//调用函数const res = context.fn(...args)// 删除context对象上的fn属性if (temp) {context.fn = temp} else {delete context.fn}// 返回运行结果return res
}

验证

let num=1;
let obj={num:2,fn:'this is obj.fn'
}function test(a,b,c,d){console.log(this.num,'test参数:',a,b,c,d)
}// 调用myCall函数
test.myCall(obj,4,3,2,1)// 检查obj本身的fn是否被修改
console.log(obj.fn)

以上验证代码运行结果为:

说明手写的myCall方法可以修改this指向,并且obj本身的fn未被修改

apply()

原理:

基本原理和call类似,区别就是对参数对处理不同:

call方法接受的参数是一个参数列表,而apply方法接受的是一个包含多个参数的数组。

这里我们就可以用到上文说的类数组对象arguments来处理参数了

详细实现:

// 和myCall的不同之处1:参数
Function.prototype.myApply=function(context){if(typeof this!== 'function'){throw new TypeError('type error')}var context = context || windowvar temp = nullif (context.fn) {temp = context.fn}context.fn = thislet res;// 和myCall的不同之处2:参数的处理// 判断第二个参数是否存在if (arguments[1]) {res = context.fn(...arguments[1])}else{res = context.fn()}// 删除context对象上的fn属性if (temp) {context.fn = temp} else {delete context.fn}// 返回运行结果return res
}

 验证:

let num=1;
let obj={num:2,fn:'this is obj.fn'
}function test(a,b,c,d){console.log(this.num,'test参数:',a,b,c,d)
}// 调用myCall函数
test.myApply(obj,[1,2,3,4])// 检查obj本身的fn是否被修改
console.log(obj.fn)

 运行结果为:

 

bind()

原理:

bind需要考虑一种情况,就是bind返回的函数作为构造函数使用的时候,bind绑定的this失效,但是参数依旧有效。

如何区分bind是正常使用还是当构造函数使用呢?根据this判断就行了。因为函数的this指向取决于如何调用(上文讲到过)。

1.当构造函数的时候,this指向新建的实例对象(此时this的prototype在该构造函数上)。

2.正常使用,this指向window

详细实现:

Function.prototype.myBind=function(context){if(typeof this!== 'function'){throw new TypeError('type error')}// 获取参数var args0 =[...arguments].slice(1);// 保存this,如果作为构造函数使用,此时this会指向实例fn=this;//返回更改this指向的函数return function Fn(...args){//如果是new的形式来使用绑定函数的if(this instanceof Fn) return new fn( ...args0,...args)//如果是普通函数的形式来使用绑定函数的else return fn.call(context, ...args0,...args);}
}

验证:

function Point(x, y) {this.x = x;this.y = y;
}// 情况1:正常调用bind函数
var testObj = {};
var YAxisPoint = Point.myBind(testObj, 0 );
YAxisPoint(1)
console.log(testObj)// 情况2:bind返回函数作为构造函数
// 此时之前绑定的指向testObj的this会失效,会重新指向新的对象实例,但是参数会继续有效
let newObj=new YAxisPoint(2);
console.log(newObj)

运行结果为

符合预期

 

相关文章:

手写实现call() apply() bind()函数,附有详细注释,包含this指向、arguments讲解

手写实现call() apply() bind()函数是很经典的问题,但是能掰扯清楚的文章确实不算多,于是笔者才决定写下本文,希望能给读者带来一些启发,如有错误欢迎指正。 目录 补充知识 函数中的this指向 类数组对象arguments call() 原理…...

MySQL中日期、时间直接相减的坑

前言 在牛客网上写一道 SQL 题时,需要计算两个日期之间相隔的秒数,我在写的时候直接将两个日期进行相减,得出来的值却不是相差的秒数。 情景再现 我在 MySQL 中进行了测试,得出的结论是:如果日期类型直接相减&#…...

漏洞发现-web应用发现探针类型利用(43)

关于在真实环境下面,这个漏洞该如何发现 这里老师把它分成了三块第一类是 #已知cms 如常见的dedecms,discuz,wordpress等源码结构,这些都是网上比较知名的php源码的cms的名称,这是我们在国内常见的几个程序&#xf…...

专门针对开发人员,攻击者利用Rust获取操作系统信息

近日,研究人员在 Rust 编程语言的 crate 注册表中发现了一些恶意软件包,专门针对开发人员。 Phylum 在上周发布的一份报告中称,这些库是由一个名为 "amaperf "的用户在 2023 年 8 月 14 日至 16 日之间上传的。现已删除的软件包名…...

PHP8的箭头函数-PHP8知识详解

php 7.4 引入了箭头函数(Arrow Functions),并在 PHP 8 中得到了进一步改进和扩展。 箭头函数是一种更简洁的匿名函数形式,它们提供了一种更便捷的方式来定义轻量级的、单行的回调函数。 箭头函数的语法如下: fn (参…...

初识PHP编程:探索Web开发的起点

初识PHP编程:探索Web开发的起点 PHP(Hypertext Preprocessor)是一种广泛使用的服务器端脚本语言,专门用于Web开发。它的强大功能和简单易学的语法使得它成为初学者和专业开发者的首选。在本文中,我们将探索什么是PHP&…...

Git——Windows平台创建gitee私有仓库详解

目录 1. 安装git 2. gitbash配置 2.1 设置 2.2 生成key 2.3 项目管理 2.3.1 本地新建 2.3.2 clone远程仓库的工程到本地改文件 1. 安装git 默认安装。 2. gitbash配置 2.1 设置 打开gitbash,设置用户名和邮箱: git config --global user.name …...

Git基础教程-常用命令整理:学会Git使用方法和错误解决

目录 一、了解Git的基本概念 二、Git的安装和配置 Git的安装 Git的配置 用户信息 文本编辑器 差异分析工具 查看配置信息 三、Git的基本操作 基本原理 基本操作命令 基本操作示例 场景一:创建新仓库 场景二:拉取并编辑远程仓库 四、常见问…...

Ops实践 | 国产化KylinOS系统中快速部署企业内部高性能DNS服务器、时间同步服务器 (精选)...

各位看友,由于微信公众号推送机制改变,现在需要设置为星标才能收到的本公众号推送消息哟。关注回复【学习交流群】加入【安全开发运维】答疑交流群 请朋友们【多多点击文中的广告】,支持作者更新更多文章。 目录: 本文为作者原创文章&#xf…...

stm32之IIC协议

主要通过两个层面来讲:物理层、协议层。 IIC是一个同步半双工串行总线协议。 一、物理层(通信模型) 1、最早是飞利浦公司开发的这个协议,最早应用到其产品上去。 2、两线制(两根信号线) 其中SCL为时钟…...

范式 事务 多表查询

范式 概念:设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须遵循前边的所有范式要求 第一范式: 数据库表的每一列都是不可分割的基本数据项 这样子就不满足第一范式 这样子就满足第一范式 存在问题: 数…...

基于白鲸算法优化的BP神经网络(预测应用) - 附代码

基于白鲸算法优化的BP神经网络(预测应用) - 附代码 文章目录 基于白鲸算法优化的BP神经网络(预测应用) - 附代码1.数据介绍2.白鲸优化BP神经网络2.1 BP神经网络参数设置2.2 白鲸算法应用 4.测试结果:5.Matlab代码 摘要…...

java并发编程 ReentrantLock详解

文章目录 1 概要2 相关文章3 例子4 方法详解4.1 lock()4.2 unlock()4.3 tryLock()4.4 其他公平锁 总结 1 概要 ReentrantLock 通过实现Lock接口的行为,提供锁机制。但是实现委托给了内部的Sync,Sync extends AbstractQueuedSynchronizer,继承…...

Java获取文件内容IO流

文章目录 InputStream和ReaderScannerNIO外传 一般读取文件类的使用字符流即可 InputStream和Reader InputStream和Reader是Java IO中的两个重要的抽象基类,InputStream是二进制流,Reader是字符流。使用InputStream或者Reader读取文件内容可以帮助我们…...

Java后端开发面试题——集合篇

ArrayList底层的实现原理是什么 底层数据结构 ArrayList底层是用动态的数组实现的 初始容量 ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10 扩容逻辑 ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组 添加逻…...

如何允许远程访问MySQL

许多网站和应用程序一开始都将web服务器和数据库后端托管在同一台机器上。不过,随着时间的推移,这样的设置可能会变得繁琐和难以扩展。一种常见的解决方案是通过设置远程数据库来分离这些功能,允许服务器和数据库在各自的机器上按自己的速度增…...

001图机器学习与图神经网络简介

文章目录 一. 无处不在的图二. 如何对图数据做信息挖掘三. 图神经网络四. 图机器学习常用的编程工具五. 图的可视化工具六. 常见的图数据库七. 图机器学习的应用举例八. 结束语 一. 无处不在的图 一切具有关联关系的数据都可以用图来表示。比如:交通网、知识图谱、…...

万级数据优化EasyExcel+mybatis流式查询导出封装

文章目录 前言.万级数据优化一. 直接上流式查询封装工具代码二. 传统分页导出查询三. 流式查询概念游标查询 前言.万级数据优化 我们不妨先给大家讲一个概念,利用此概念我们正好给大家介绍一个数据库优化的小技巧: 需求如下:将一个地市表的数…...

Unity——脚本序列化

在介绍序列化之前,我们先来了解一下为什么要对数据进行序列化 数据序列化有以下几个主要的应用场景和目的: 1. 持久化存储:序列化可以将对象或数据结构转换为字节序列,使得其可以被存储在磁盘上或数据库中。通过序列化&#xff…...

es(Elasticsearch)介绍

学习es可以参考mysql(相比mysql而言,es所需的cpu、内存更多) 什么是Elasticsearch Elasticsearch简称es,是由Elastic和search组成。Elastic的意思是有弹性的,search的意思是搜索。 弹性:es是一个天生支持分…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制&#xff0…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM&#xff09…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...