手写实现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的名称,这是我们在国内常见的几个程序…...

专门针对开发人员,攻击者利用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服务器、时间同步服务器 (精选)...
各位看友,由于微信公众号推送机制改变,现在需要设置为星标才能收到的本公众号推送消息哟。关注回复【学习交流群】加入【安全开发运维】答疑交流群 请朋友们【多多点击文中的广告】,支持作者更新更多文章。 目录: 本文为作者原创文章…...

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. 持久化存储:序列化可以将对象或数据结构转换为字节序列,使得其可以被存储在磁盘上或数据库中。通过序列化ÿ…...
es(Elasticsearch)介绍
学习es可以参考mysql(相比mysql而言,es所需的cpu、内存更多) 什么是Elasticsearch Elasticsearch简称es,是由Elastic和search组成。Elastic的意思是有弹性的,search的意思是搜索。 弹性:es是一个天生支持分…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...