码上【call,apply,bind】的手写

news2024/11/24 9:00:55
一、call
(1)官方用法

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

语法:function.call(要绑定的this值,参数,参数,…)。不一定这些参数都需要,这些参数都是可选的,返回值:使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

// 不指定参数
var nickname = 'global'
​
function getName() {console.log(this.nickname);
}
getName() //global
getName.call() //global 

在严格模式下,this 的值将会是 undefined。当不给call指定参数时,相当于将不改变this的指向,在哪调用的就指向哪儿,即没有传递第一个参数,this的值将会被绑定为全局对象。

// 指定第一个参数
var name = 'cat'
var obj = {name: 'dog'
}
​
function getName() {console.log(this.name);
}
getName() //cat
getName.call(obj) //dog
console.log(obj)//{ name: 'dog'} 

当指定第一个参数时,第一个参数代表新的this值,当调用方法时,该方法的this值会绑定到obj对象,于是会访问这个对象的name属性。

//指定第一个参数和其他参数
var obj = {user: 'Ducky',fn: function(a, b) {console.log(a + b);console.log(this.user);}
}
var b = obj.fn;
b.call(obj, 1, 2);
// 3
// Ducky 

除了第一个参数,其余参数都是用来做一些必要的运算等。运行到这,应该不难发现,使用Function.call()的时候,函数是谁执行的?是使用call绑定对象的之后,call也把函数执行了。

相信你已经会用了,试着写写实现原理。

(2)实现原理

实现call的关键在于:

第一:如何给函数绑定新的this

第二:如何在绑定完this后把函数也给执行完毕?

掌握了这两个关键,那么一切都有迹可循了。接下来看看换成自己手写的是不是一样的效果:

Function.prototype.my_call = function(context) {// 如何实现绑定新的thiscontext.fn = this //context['fn']=this// 如何在调用call时把调用call的函数也执行context.fn()
} 

是不是大吃一惊?就实现了?是的,就是实现了,核心原理就是这两个关键,不信可以测试一下:

var name = 'cat'
var obj = {name: 'dog'
}
​
function getName() {console.log(this.name);
}
getName() //cat
getName.my_call(obj) //dog
console.log(obj)//{ name: 'dog', fn: [Function: getName] } 

真的绑定成功了!接下来继续做点优化,优化也有几个关键点:

第一:非得是函数才可以调用call

第二:call除了接收第一个参数(新的this)外,还可以接收一个参数列表;

第三:官方call调用后,不会改变新的thisobj)的结构,在上述代码中obj内部新增了一个属性fn

第四:当call不传第一个参数时,需指向全局对象(window);

第五:调用call时,需得有返回值;

第六:当将this挂载到新的this上时,后者已经存在该属性的情况下,还是会改变后者的结构。

终极实现原理:

Function.prototype.my_call = function(context, ...args) {if (typeof this !== 'function') throw new TypeError('error')context = context || 'window'let fn = Symbol('fn')context[fn] = this //context.fn = this const res = context[fn](...args)delete context[fn]return res
} 

虽然考虑了六种情况,但是代码还是很可人,这回,它就是与官方的源码一样的效果和功能了,简直不要太完美~

二、apply
(1)官方用法

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

语法:function.apply(要绑定的this的值,[参数,参数,参数,…]),返回值:调用有指定 this 值和参数的函数的结果。

可见,它跟call就只有一个区别:提供参数的方式不同。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['dog', 'cat']),或数组对象,如 fun.apply(this, new Array('dog', 'cat')),还可以使用 arguments 对象作为 argsArray 参数。arguments 是一个函数的局部变量。它可以被用作被调用对象的所有未指定的参数。这样,在使用 apply 函数的时候就不需要知道被调用对象的所有参数及其个数。可以直接使用 arguments 来把所有的参数传递给被调用对象。被调用对象接下来就负责处理这些参数,要解构,要切割,要指定索引都可以。

既然apply的用法和call几乎相同,只是除第一个参数外,其余参数传递方式不一样,那么我们直接在my_call的手写上修改传递参数的形式即可。

(2)实现原理
Function.prototype.my_apply = function(context, args) {if (typeof this !== 'function') throw new TypeError('error')context = context || 'window'let fn = Symbol('fn')context[fn] = this //context.fn = this const res = context[fn](...args)delete context[fn]return res
} 

这。。。。,还是测试一下吧:

var obj = {user: 'Ducky',fn: function(a, b) {console.log(a + b);console.log(this.user);}
}
var b = obj.fn;
b.my_apply(obj, [1, 2]);
// 3
// Ducky 

好了,成功了,该考虑的已经在手写call的时候考虑过了,就是这么的干净利落。

三、bind
(1)官方用法

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

语法:function.bind(要绑定的this值,参数1,参数2,参数3)。

这里说明,第一个参数是如果为空,或者为null||undefined,执行作用域的this将被视为形函数的的this值,其余参数(当目标函数被调用时,被预置入绑定函数的参数列表中的参数)可有可无,也可在返回的新函数里面传递。返回值是返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

举个例子:

var name = 'cat'
var obj = {name: 'dog',fn: foo
}
function foo(a, b) {console.log(this.name, a + b);return a + b
}
const bar = foo.bind(obj)
bar(3, 4)
//dog 7 
(2)实现原理

从上述例子可以看出,bind()会创建一个新的绑定函数bar

实现bind函数的关键在于:

第一:如何新建一个绑定函数;

第二:如何将拿到在call()传递的参数和调用新建的绑定函数传递的参数并结合

第三:如果使用new操作符操作新建的那个绑定函数,this又该如何指向

先讨论以下前两个关键,新建一个绑定函数直接在call内部返回一个函数即可,将两个地方的传递的参数都传递给执行绑定函数。

Function.prototype.my_bind = function(context, ...args1) {// 保存外部函数的thisconst _this = thisreturn function bound(...args2) {//返回一个新函数
        //返回值return _this.call(context, ...args1, ...args2)}
} 

不要惊讶在实现bind函数内部用的是call||apply,大不了用刚刚手写的my_call||my_apply。大体上就实现了,测试一下:

var name = 'cat'
var obj = {name: 'dog',fn: foo
}
​
function foo(a, b) {console.log(this.name, a + b);return a + b
}
const bar = foo.my_bind(obj)
bar(3, 4)
//dog 7 

再来考虑第三个问题:

//官方bind
const bar = foo.bind(obj)
new bar(3, 4)//cat 7
​
//手写的my_bing
const bar = foo.my_bind(obj)
new bar(3, 4) //dog 7 

从上可以看出,当使用new操作符操作新的函数bar时,官方的bind会忽略绑定的this值,但是前置参数依然会提供给铭记函数,而我们手写的my_bind原封不动,这肯定得改!

现在无非是,当使用new运算符构造新建的绑定函数bar时,foo.bind(obj)中,foo的·this指向不指向obj,也不指向全局对象,而是会指向实例对象new bar()的执行作用域,接下来可以理一下思路:

//目标:(new bar)._proto_==foo.prototype
//即如果新建的绑定函数被new,bind的调用函数就会变成实力对象的构造函数
​
//接下来的操作都是在my_bind函数内部操作
//借助一个辅助函数
const help=function(){}
bound.prototype=new help()//继承到了foo(bind的调用函数)的原型
if(this.prototype){//this指的是bind的调用函数
   help.prototype=this.prototype
 }
​ 

通过一通操作,得到new help()._proto_==help.prototype=this.prototype==bound.prototypethis是什么,取决于调用bind函数的函数是什么,在这里是foo,所以new bound()._proto_==bound.prototype=foo.prototype,那么,调用bind函数的this到底指向什么取决于,新建的绑定函数bar有没有被new,如果没有,则看传递的第一个参数,第一个参数为空则为全局对象,不为空则为指定的对象;如果被new了,那么调用nind函数的this指向新建的绑定函数的执行作用域,bind最终实现方式是:

Function.prototype.my_bind = function(context, ...args1) {if (typeof this !== 'function') {throw new TypeError('error')}context = context || windowconst _this = thisconst help = function() {}if (this.prototype) {help.prototype = this.prototype}const bound = function(...args2) {return _this.call(this instanceof help ? this : context,...args1,...args2)}bound.prototype = new help()return bound
} 

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

寿险业务系统异常处理方案

我们的系统使用的java语言开发,基于Spring框架搭建的业务中台,在讨论业务系统异常处理策略之前,先把java的异常机制进行简单说明。 一、Java的异常机制 1.Java异常分类 【Error】是系统错误或者编译错误,常见的虚拟机运行错误、…

2023,考个软考中级证书稳妥深圳入户,5月考试8月办入户

最新消息!最新消息!最新消息! 2023年2月8日,深圳市发展和改革委员会深圳市公安局深圳市人力资源和社会保障局关于印发《深圳市积分入户办法》的最新通知↓ 来源《深圳市发展和改革委员会》 该积分入户将于2023年2月15日正式实施&…

C++与Lua交互实例 -- 矩阵的加减乘除(版本一)

C与Lua交互实例 – 矩阵的加减乘除(版本一) 关于lua中封装的类模板以及相关知识可参考以下链接: https://ufgnix0802.blog.csdn.net/article/details/128854786 https://ufgnix0802.blog.csdn.net/article/details/128827673 https://ufgnix0…

Python深度学习实战PyQt5窗口切换的堆叠布局示例详解

本文以堆叠窗口控件为例,详细介绍堆叠布局的界面设计和程序实现过程,通过案例带小白创建一个典型的堆叠布局多窗口切换程序软件项目中经常需要多种不同的图形界面,以适应不同的任务场景。选项卡控件(QTackedWidget)通过…

[Java 进阶] synchronized 与 ReentrantLock的区别

祝一切顺利~ 文章目录synchronized 与 ReentrantLock的区别synchronized 与 ReentrantLock的区别 1.ReentrantLock的锁是公平锁,synchronized是非公平锁.(公平锁是指当锁被释放时,由等待时间最长的线程获取锁.非公平锁是锁被释放时,所有线程不论等待时间长短,都一起去竞争) 2.…

小白系列Vite-Vue3-TypeScript:009-屏幕适配

上一篇我们介绍了ViteVue3TypeScript项目中mockjs的安装和配置。本篇我们来介绍屏幕适配方案,简单说来就是要最大程度上保证我们的界面在各种各样的终端设备上显示正常。通用的屏幕适配方案有两种:① 基于rem 适配(推荐,也是本篇要…

线下沙龙|从VUCA到BANI,找到你的“第二曲线”

冷战之后,VUCA盛行,世界已复杂到无可言表;然而,我们正在拼命地理解和适应“VUCA时代”,却又被迷迷糊糊、跌跌撞撞地推进了“BANI时代”。 未来之路在何方?在脚下,却不知该如何去迈? …

elasticsearch使用painless的一些简单例子

文章目录1、背景2、准备数据2.1 mapping2.2 插入数据3、例子3.1 (update)更新文档 id1 的文档,将 age 加 2岁3.2 (update_by_query)如果 province 是北京的话,就将 age 减少1岁3.3 (ctx.op)如果张三的年龄小于20岁就不处理,否则就删除这个文档…

MLX90316KGO-BDG-100-RE传感器 旋转位置 角度测量

介绍MLX90316是Tria⊗is旋转位置传感器,提供在设备表面旋转的小偶极磁铁(轴端磁铁)的绝对角位置。得益于其表面的集成磁集中器(IMC),单片设备以非接触式方式感知应用磁通量密度的水平分量。这种独特的传感原理应用于旋转位置传感器,可在机械(…

(1分钟速通面试) 矩阵分解相关内容

矩阵分解算法--总结QR分解 LU分解本篇博客总结一下QR分解和LU分解,这些都是矩阵加速的操作,在slam里面还算是比较常用的内容,这个地方在isam的部分出现过。(当然isam也是一个坑,想要出点创新成果的话 可能是不太现实的 短期来讲 哈…

【电商】订单系统--售后的简易流程与系统关系

用户进行了订单签收并不意味着终结,这只是一个新的开始,因为商品送达后可能会由于运输过程包装或商品有破损,商品本质量并非商品详情中所描述的那样等各种原因使用户进行退货或换货;还有一种场景是用户签收后发现有的商品漏发、少…

线性卡尔曼滤波详解

自动驾驶面临的第一个问题:从哪里来,到哪里去?要解决这个问题,自动驾驶汽车首先需要准确的知道自己在地图上的位置。理所当然的我们可以想到通过GPS来进行定位,但获取GPS信号需要跟卫星进行通信,这就导致它…

简单的洗牌(数据结构系列4)

目录 前言: 1.思想 1.1基本框架的搭建 1.2洗牌操作 1.3揭牌 2.代码运行结果截图 结束语: 前言: 在上一次的博客中小编与大家分享了ArrayList的模拟实现和一些具体的使用方法,那么接下来我们通过一个简单的洗牌练习来巩固一…

Java创建枚举类并定义使用枚举项

前言 在项目开发中,常量和枚举都是我们经常使用的技巧。都是为了提高开发效率。常量我们一般都会创建一个常量类,里面放一些经常使用的变量,例如: 枚举的定义和使用相比于常量,也是有异曲同工之巧的,下面就…

梅开二度的 axios 源码阅读,三千字详细分享功能函数,帮助扩展开发思维

前言 第一遍看 axios 源码,更多的是带着日常开发的习惯,时不时产生出点联想。 第二遍再看 axios 源码,目标明确,就是奔着函数来的。 当有了明确清晰的目标,阅读速度上来了,思绪也转的飞快。 按图索骥&a…

day 15 第六章二叉树

层序遍历 102.二叉树的层序遍历107.二叉树的层次遍历II199.二叉树的右视图637.二叉树的层平均值429.N叉树的层序遍历515.在每个树行中找最大值116.填充每个节点的下一个右侧节点指针117.填充每个节点的下一个右侧节点指针II104.二叉树的最大深度111.二叉树的最小深度 226.翻转二…

速腾rshelios 5515惯导时间同步

目前接触过两种雷达和惯导同步的方式: 1.惯导输出gprms和pps信号给米文系统,雷达驱动从系统里读取时间。 2.惯导输出gprms和pps信号给雷达,雷达驱动从雷达数据读取时间。 GPRMS和PPS的内容参考:STM32模拟GPS输出PPS、GPRMC与VLP…

多传感器融合定位九-基于滤波的融合方法Ⅰ其一

多传感器融合定位九-基于滤波的融合方法Ⅰ其一1. 滤波器的作用2. 概率基础知识2.1 概率、概率密度2.2 联合概率密度2.3 条件概率密度2.4 贝叶斯公式2.5 贝叶斯推断2.6 高斯概率密度函数2.7 联合高斯概率密度函数2.8 高斯随机变量的线性分布1. 滤波器的作用 滤波器的本质&#…

编译与链接------《程序员的自我修养》

本篇整理于《程序员的自我修养》一书中编译与链接相关知识,整理的目的是为了更加深入的了解编译于链接的更多底层知识,面对程序运行时种种性能瓶颈我们束手无策。我们看到的是这些问题的现象,但是却很难看清本质,所有这些问题的本质就是软件运…

Allegro如何使用Vertext命令修改丝印线段的形状操作指导

Allegro如何使用Vertext命令修改丝印线段的形状操作指导 在用Allegro画丝印线段的时候,如果画了一段不是自己需要形状的线段,无需删除重画,可以用Vertext命令直接编辑 如下图 修改前 修改后 具体操作如下 选择Edit