JS进阶第一篇:手写call apply bind

news2024/11/26 18:33:31

文章目录

  • 手写call apply bind
    • 深入理解 call 方法
    • 手写call
    • 手写apply
    • 手写bind

手写call apply bind

深入理解 call 方法

call 理解了,apply和bind就都迎刃而解了,他们都是大同小异。在此对callapply不做过多的定义性解释,先来看下调用了call后谁是那个被执行的方法,直接代码示例:

function fn1 () {
    console.log(1);
};
function fn2 () {
    console.log(2);
};
fn1.call(fn2);//1

执行fn1.call(fn2);控制台会打印1,这里可以说明fn1调用call后被执行的方法还是fn1。一定要弄清楚谁是这个被执行的方法,就是调用call的函数,而fn2现在的身份是替代window作为fn1的直接调用者,这是理解call和apply的关键,也可以运行下fn2.call(fn1);
再来个代码示例:

var obj1 = {
    num : 20,
    fn : function(n){
        console.log(this.num+n);
    }
};
var obj2 = {
    num : 15,
    fn : function(n){
        console.log(this.num-n);
    }
};
obj1.fn.call(obj2,10);//25

执行obj1.fn.call(obj2,10);控制台会打印25,call在此的作用其实很简单,就是在执行obj1.fn的时候把这个fn的直接调用者由obj1变为obj2,obj1.fn(n)内部的this经过call的作用指向了obj2,所以this.num就是obj2.num,10作为执行obj1.fn时传入的参数,obj2.num是15,因此打印出的值是15+10=25。
所以我们可以这样理解:call的作用是改变了那个被执行的方法(也就是调用call的那个方法)的直接调用者!而这个被执行的方法内部的this也会重新指向那个新的调用者,就是call方法所接收的第一个obj参数。还有两个特殊情况就是当这个obj参数为null或者undefined的时候,this会指向window。

手写call

Function.prototype.myCall = function (context) {
      // 先判断调用myCall是不是一个函数
      // 这里的this就是调用myCall的
      if (typeof this !== 'function') {
        throw new TypeError("Not a Function")
      }
 
      // 不传参数默认为window
      context = context || window
 
      // 保存this
      context.fn = this
 
      // 保存参数
      let args = Array.from(arguments).slice(1)
      //Array.from 把伪数组对象转为数组,然后调用 slice 方法,去掉第一个参数
 
      // 调用函数
      let result = context.fn(...args)
 
      delete context.fn
 
      return result
 
    }

手写apply

Function.prototype.myApply = function (context) {
      // 判断this是不是函数
      if (typeof this !== "function") {
        throw new TypeError("Not a Function")
      }
 
      let result
 
      // 默认是window
      context = context || window
 
      // 保存this
      context.fn = this
 
      // 是否传参
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
      delete context.fn
 
      return result
    }

手写bind

在实现手写bind方法的过程中,看了许多篇文章,答案给的都很统一,准确,但是不知其所以然,所以我们就好好剖析一下bind方法的实现过程。

我们先看一下bind函数做了什么:

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

读到这里我们就发现,他和 apply , call 是不是很像,所以这里指定 this 功能,就可以借助 apply 去实现:

Function.prototype.myBind = function (context) {
    // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
    const self = this;
    // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
    // 这里产生了闭包
    const args = Array.from(arguments).slice(1)
    return function () {
        // 这个时候的 arguments 是指 myBind 返回的函数传入的参数
        const bindArgs = Array.from(arguments)
        // 合并
        return self.apply(context, args.concat(bindArgs));
    };
};

大家对这段代码应该都能看懂,实现原理和手写 call , apply 都很像,因为 bind 可以通过返回的函数传参,所以在 return 里面获取的 bindArgs 就是这个意思,然后最后通过 concat 把原来的参数和后来传进来的参数进行数组合并。

我们来看一下结果:

const person = {
    name: 'zyj'
}
 
function man(age) {
    console.log(this.name);
    console.log(age)
}
 
const test = man.myBind(person)
test(18)//zyj 18

现在重点来了,bind 区别于 call 和 apply 的地方在于它可以返回一个函数,然后把这个函数当作构造函数通过 new 操作符来创建对象。

我们来试一下:

const person = {
    name: 'zyj'
}
 
function man(age) {
    console.log(this.name);
    console.log(age)
}
 
const test = man.myBind(person)
const newTest = new test(18) // zyj 18

这是用的我们上面写的 myBind 函数是这个结果,那原生 bind 呢?

const person = {
    name: 'zyj'
}
 
function man(age) {
    console.log(this.name);
    console.log(age)
}
 
const test = man.bind(person)
const newTest = new test(18) // undefined 18
由上述代码可见,使用原生 bind 生成绑定函数后,通过 new 操作符调用该函数时,this.name 是一个 undefined,这其实很好理解,因为我们 new 了一个新的实例,那么构造函数里的 this 肯定指向的就是实例,而我们的代码逻辑中指向的始终都是 context ,也就是传进去的参数。

所以现在我们要加个判断逻辑:

Function.prototype.myBind = function (context) {
    // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
    const self = this;
    // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
    // 这里产生了闭包
    const args = Array.from(arguments).slice(1)
    const theBind = function () {
        const bindArgs = Array.from(arguments);
    
        // 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例
        // 当作为普通函数时,将绑定函数的 this 指向 context 即可
        // this instanceof fBound 的 this 就是绑定函数的调用者
        return self.apply(
          this instanceof theBind ? this : context,
          args.concat(bindArgs)
        );
      };
      return theBind;
};

现在这个效果我们也实现了,那我们的 myBind 函数就和其他的原生 bind 一样了吗?来看下面的代码:

const person = {
    name: 'zyj'
}
function man(age) {
    console.log(this.name);
    console.log(age)
}
man.prototype.sayHi = function() {
    console.log('hello')
}
const test = man.myBind(person)
const newTest = new test(18) // undefined 18
newTest.sayHi()

如果 newTest 是我们 new 出来的 man 实例,那根据原型链的知识,定义在man的原型对象上的方法肯定会被继承下来,所以我们通过 newTest.sayHi 调用能正常输出 hello 么?

alt

该版代码的改进思路在于,将返回的绑定函数的原型对象的 proto 属性,修改为原函数的原型对象。便可满足原有的继承关系。

Function.prototype.myBind = function (context) {
    // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
    const self = this;
    // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
    // 这里产生了闭包
    const args = Array.from(arguments).slice(1);
    const theBind = function () {
        const bindArgs = Array.from(arguments);
    
        // 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例
        // 当作为普通函数时,将绑定函数的 this 指向 context 即可
        // this instanceof fBound 的 this 就是绑定函数的调用者
        return self.apply(
          this instanceof theBind ? this : context,
          args.concat(bindArgs)
        );
      };
      theBind.prototype = Object.create(self.prototype)
      return theBind;
};

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

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

相关文章

opencv阈值图像Threshold方法

图像阈值 固定阈值,自适应阈值,Otsu 二值化等 全局阈值和局部阈值 一、图像二值化 定义:图像的二值化,就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。 灰度值0&…

热门Java开发工具IDEA入门指南——导出项目到Eclipse

IntelliJ IDEA,是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能是非常强大的。 上文…

打工人,这里有一份述职技巧,请查收

大家好,马上到年底了,有多少小伙伴正在期待着述职邮件,毕竟收到述职邮件,也就意味着有机会升职加薪。有没有跟糖糖一样,没收到邮件的? 工作要善于总结,也要善于表达,如何在限时内将…

跨平台应用开发进阶(四十)自定义插件及引用

文章目录一、前言二、插件制作三、离线插件集成应用示例四、拓展阅读一、前言 正如将可复用功能封装为自定义组件以供他人使用一样,在uni-app开发框架中提供了另一种形式的自定义插件,并可将该插件提交至uni-app插件市场。 二、插件制作 制作插件前&a…

前端 单元测试介绍 - 以及在项目中使用 (史上最全)

前言 我们前端开发过程中,编写测试代码,有以下这些好处: 更快的发现bug,让绝大多数bug在开发阶段发现解决,提高产品质量 比起写注释,单元测试可能是更好的选择,通过运行测试代码,观…

ARM 异常处理方式简单介绍

一、什么是异常 正常工作之外的流程都叫异常; 也就是说,除了用户模式和系统模式外,其他情况都是异常,见下图: 异常会打断正在执行的工作,并且一般我们希望异常处理完成后继续回来执行原来的工作&#xff…

3-azido-1-Propanamine,88192-19-2,3-叠氮基丙胺 性质特点有哪些?

●中文名:3-叠氮基丙胺,3-叠氮基-丙胺 ●英文名:3-azido-1-Propanamine ●外观以及性质: 西安凯新生物科技有限公司供应的:​3-azido-1-Propanamine为淡黄色或无色油状,含有叠氮基团,叠氮基可以…

Node.js 入门教程 23 使用 npm 的语义版本控制 24 卸载 npm 软件包 25 npm 全局或本地的软件包

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录,不存在任何商业用途,如侵删 文章目录Node.js 入门教程23 使用 npm 的语义版本控制24 卸载 npm 软件包25 npm 全局或本地的软件包23 使用 npm 的语义版本控制 如果 Node.js 软件…

第147篇 笔记-预言机(Oracle)

定义:区块链预言机是将区块链连接到外部系统的实体,从而使智能合约能够基于现实世界的输入和输出执行。 预言机为分散的 Web3 生态系统提供了一种访问现有数据源、遗留系统和高级计算的方式。去中心化预言机网络(DON)支持创建混合…

[附源码]计算机毕业设计springboot“小世界”私人空间

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

Java学历、技术哪个更重要?学历不好还能进大厂吗?

Java程序员的入行门槛并不高,并不看重你的学历和其他各方面,唯一看重的就是你技术是否过硬,能否独立参与到企业级开发的项目中去,说简单点入行只看技术。但是你如果想要长远发展服日后走上管理岗位,最好还是自考个本科…

使用 SwiftUI 构建可搜索列表,为您的 iOS 应用程序创建具有自动完成功能的可搜索列表(教程含源码)

设计新应用程序时面临的一大挑战是确保您的用户可以轻松浏览内容。如果体验太难或花费太多时间,无论您的内容有多好,很多用户都会转向另一个应用程序选项或放弃。 期望用户滚动浏览一长串选项是不切实际的,添加搜索功能可以极大地改善用户体验。更进一步,在用户键入时让列…

2.RabbitMQ安装

2.RabbitMQ安装 注意:安装时使用的系统是CentOS-7,MQ基本概念和RabbitMQ的相关知识请查看写的文章。 1、安装依赖环境 在线安装依赖环境: yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel…

17、Health Check 健康检查

强大的自愈能力是kubernetes容器编排引擎的重要特性。自愈的默认实现方式是自动重启发生故障的容器。除此之外,还可通过Liveness和Readiness探测机制设置更精细的健康检查,进而实现如下要求:零停机部署避免部署无效的镜像更加安全的滚动升级一…

有没有免费的视频剪辑软件?快来看看这些视频裁剪软件

我们有时候将视频拍好后,会觉得视频中有些画面的边缘出现了瑕疵,就想要将那些边缘裁剪掉,但是却不知道要怎么操作才能裁剪视频的画面。其实想要裁剪视频的画面很简单,我们只需要借助一些视频处理工具就可以实现裁剪视频画面的操作…

Swift 周报 第十八期 |技术汇总

前言 本期是 Swift 编辑组自主整理周报的第九期,每个模块已初步成型。各位读者如果有好的提议,欢迎在文末留言。 欢迎投稿或推荐内容。目前计划每两周周一发布,欢迎志同道合的朋友一起加入周报整理。 蝴蝶的生命之所以如此短暂&#xff0c…

解决git中出现的“fatal ‘xxxx‘ does not appear to be a git repository”错误的方法

今天来分享一下我在使用git中出现的一个错误提示,话不多说,我们直接来分析~ 这个错误是我在通过SSH方式pull远程仓库时候出现的,错误提示如下: fatal: xxx(你的仓库别名) does not appear to be a git repository fatal: Could n…

【C++学习】string的模拟实现

🐱作者:一只大喵咪1201 🐱专栏:《C学习》 🔥格言:你只管努力,剩下的交给时间! 上篇文章中本喵介绍了C标准库中string类的使用,下面本喵来模拟实现一下string类。库中的s…

【Spring框架】爆gan两万六千字,助你通关IoC和DI

✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。 🍎个人主页:Hhzzy99 🍊个人信条:坚持就是胜利! 💞当前专栏:【Spring】 🥭本文内…

navicat连接mysql数据库

一、打开navicat软件 二、创建一个测试连接 1、点击【连接】,选择【MySQL】 2、创建连接。 3、连接出现报错 三、解决方式: 1、键盘上wins r 同时按,输入cmd,调出命令行窗口。 2、通过cmd登陆mysql 3、输入以下语句修改密码 更…