JavaScript call,callee,caller,apply,bind之间的区别

news2024/12/24 8:16:08
(现实是此岸,梦想是彼岸,中间隔着湍急的河流,行动则是架在河上的桥梁。——克雷洛夫)

在这里插入图片描述

call

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

call方法可以将一个对象属性作为另一个对象方法的上下文来使用,比如以下代码。

const obj = {
  name: '张三',
  age: 20,
  getData: function () {
    return `${this.name}的年龄是${this.age}`
  }
};
const obj2 = {
  name: '李四',
  age: 50,
  getData: function () {
    return `${this.name}的年龄已经${this.age}岁了`
  },
  getData2: function (sex, address) {
    return `${this.name}的年龄已经${this.age},性别${sex}, 他家住在${address}`;
  }
};
console.log(obj.getData()); // 使用自身属性和方法执行
console.log(obj.getData.call(obj2)); // 将obj2作为obj.getData方法的上下文来执行
console.log(obj2.getData2.call(obj, '男', '北京市')); // 将obj作为obj2.getData2方法的上下文来执行

call方法可以将一个函数的上下文作为另一个函数的上下文来使用,达到继承的目的

const Father = function () {
  this.name = '父亲';
  this.age = 60;
  this.address = '北京市';
  this.sex = '男';
  this.getData = function () {
    return `名称:${this.name} - 年龄:${this.age} - 地址:${this.address} - 性别:${this.sex}`;
  };
  this.getData2 = function (hobby) {
    return `名称:${this.name} - 年龄:${this.age} - 地址:${this.address} - 性别:${this.sex} - 爱好:${hobby}`;
  };
};
const father = new Father();
console.log(father.getData()); // 输入父函数自己的上下文
const Daughter = function () {
  // 此处call要写在第一行,这样避免其他的初始化被call覆盖
  // 此处call用意为继承Father对象的上下文
  Father.call(this);
  this.name = '女儿';
  this.age = 12;
  this.address = '河南';
  this.sex = '女';
};
const daughter = new Daughter();
console.log(daughter.getData()); // 虽然Daughter函数没有getData方法,但因为使用call,this指向了Father,所以就可以使用父函数的getData方法

call方法可以将一个对象上下文作为另一个函数的上下问来使用

const obj = {
  name: '张三',
  getName: function () {
    return this.name;
  }
};
const Fun = function () {
  const data = ['姓名', this.getName()].join('');
  console.log(data);
};
Fun.call(obj); // 姓名张三

callee

MDN链接
callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。

假如我们要写一个递归计算函数,如下代码,随着函数名称的更改,内部代码的函数名也要更改。并且如果这是一个匿名函数,那么将无法进行递归。

const fun = function (x) {
  if (x === 0) {
    return;
  } else {
    return fun(x - 1);
  }
};

所以callee出现了,它可以引用该函数的函数体内当前正在执行的函数上下文,比如以下代码,通过记录内部上下文也可以达到递归的目的。

const fun2 = function (x) {
  if (x === 0) {
    return;
  } else {
    return arguments.callee(x - 1);
  }
};

但为什么callee在es5严格模式中被禁用了呢?

const global = this;
const sillyFunction = function (recursed) {
  if (!recursed)
    return arguments.callee(true);
  if (this !== global)
    console.log("This is: " + this);
  else
    console.log("This is the global");
}
sillyFunction();

通过以上的代码结果得知,每次callee得到的this都不相同,并且如果一个业务中有多个不同的arguments.callee嵌套,那么维护的成本也是巨大的。
callee的危害
所以最好的办法还是通过命名函数的方式来进行业务操作,虽然会麻烦一些,但对后期代码的维护成本大大降低,性能也更高。

caller

返回调用指定函数的函数,比如如下代码。


const Fun = function () {
  this.fun2 = function () {
    console.log(this.fun2.caller.name);
  }
};
(function Test () {
  new Fun().fun2(); // Test
})();

caller的通常在追踪业务调用链很有用,它可以将函数名和函数文本打印出来

apply

apply的作用和call相同,只不过第二个参数是数组而非多个。

const obj = {
  name: '张三',
  age: 20,
  getData: function () {
    return `${this.name}的年龄是${this.age}`
  }
};
const obj2 = {
  name: '李四',
  age: 50,
  getData: function () {
    return `${this.name}的年龄已经${this.age}岁了`
  },
  getData2: function (sex, address) {
    return `${this.name}的年龄已经${this.age},性别${sex}, 他家住在${address}`;
  }
};
console.log(obj.getData()); // 使用自身属性和方法执行
console.log(obj.getData.apply(obj2)); // 将obj2作为obj.getData方法的上下文来执行
console.log(obj2.getData2.apply(obj, ['男', '北京市'])); // 将obj作为obj2.getData2方法的上下文来执行

bind

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

MDN链接

global.x = -1;
const data = {
  x: 42,
  getX: function () {
    return this.x;
  }
};
console.log(data.getX()); // 42
const unboundGetX = data.getX;
console.log(unboundGetX());
// undefind 因为函数被赋值给unboundGetX时并没有执行,再次执行时,使用的上下文就是全局的了

const boundGetX = unboundGetX.bind(data);
// 那么为了解决this指向的问题,就可以使用.bind 通过指定参数的形式来指定this指向

console.log(boundGetX()); // 42

bind在计时器中的应用

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后,调用 'declare' 方法

bind在异步promise中的应用

假如有异步并行查询的业务场景,那么为了控制频率,减少内存和cpu占用。所以需要将异步待执行函数单独抽离出来,放入异步池中执行。

const queryAll = async () => {
  const dbCollection = {}; // mysql or mongodb
  const getUser = async () => { dbCollection.get };
  const getMobile = async () => { dbCollection.get };
  const getAddress = async () => { dbCollection.get };
  const getSex = async () => { dbCollection.get };
  const getHeight = async () => { dbCollection.get };
  const getWeight = async () => { dbCollection.get };
  // 每两个一组放入异步池,实际情况可能需要遍历并编写分组代码
  const pool= [
    [
      getUser.bind(this),
      getMobile.bind(this)
    ],
    [
      getAddress.bind(this),
      getSex.bind(this)
    ],
    [
      getHeight.bind(this),
      getWeight.bind(this),
    ]
  ];
  for (const item of pool) {
    const result = await Promise.all(item);
    // ....一些集合处理
  }
};

bind参数传递

bind和call一样都可以进行参数传递。

const data = {
  x: 42,
  getX: function (y, z) {
    return this.x + (y || 0) + (z || 0);
  }
};
const data2 = {
  x: 10,
  getX: function (y, z) {
    return this.x + (y || 0) + (z || 0);
  }
};
const unboundGetX = data.getX;
const boundGetX = unboundGetX.bind(data2, 1, 2);
console.log(boundGetX()); // 45

bind解决普通函数this指向问题


const Persion = function () { //定义构造函数
  this.age = 0; // 定义age
  setInterval(function () {
    this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
    console.log(this.age);
  }, 1000)

}
new Persion();

// 那么为了解决this问题,就可以使用提前赋值this的方式

const Persion2 = function () { //定义构造函数
  this.age = 0; // 定义age
  const that = this;
  setInterval(function () {
    that.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
    console.log(that.age);
  }, 1000)

}
new Persion2();

// 也可以使用bind的方式
const Persion3 = function () { //定义构造函数
  this.age = 0; // 定义age
  setInterval(function () {
    this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
    console.log(this.age);
  }.bind(this), 1000);
}
new Persion3();

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

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

相关文章

ChatGPT AI使用成本

LLM “经济学”:ChatGPT 与开源模型,二者之间有哪些优劣权衡?谁的部署成本更低? 太长不看版:对于日均请求在 1000 次左右的低频使用场景,ChatGPT 的实现成本低于部署在 AWS 上的开源大模型。但面对每天数以…

Qt 从入门到入土

本文目录 1. Qt 概述1.1 什么是 Qt1.2 Qt 的发展史1.3 支持的平台1.4 Qt 的版本1.5 Qt 的优点1.6 成功案例 2. 创建 Qt 项目2.1 使用向导创建2.2 手动创建2.3 .pro文件2.4 一个最简单的 Qt 应用程序2.5 Qt 命名规范和常用快捷键 3. 第一个 Qt 小程序3.1 按钮的创建3.2 对象模型…

基于springboot的学生成绩管理系统

摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&…

LeetCode第2题——两数相加(Java)

题目描述: ​ 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 ​ 请你将两个数相加,并以相同形式返回一个表示和的链表。 ​ 你可以假设除了数字 0 之外…

考研数据结构--数组与广义表

数组与广义表 文章目录 数组与广义表数组数组的基本概念性质数组的存储结构一维数组的存储结构二维数组的存储结构 特殊矩阵的压缩存储对称矩阵上三角矩阵下三角矩阵对角矩阵 稀疏矩阵定义稀疏矩阵的三元组表示稀疏矩阵的存储结构及其基本运算算法定义存入三元组三元组元素赋值…

ChatGPT in Drug Discovery

ChatGPT是OpenAI开发的一种语言模型。这是一个在大型人类语言数据集上训练的机器学习模型,能够生成类似人类语言文本。它可以用于各种自然语言处理任务,如语言翻译、文本摘要和问题回答。在目前的工作中,我们讨论了ChatGPT在药物发现中的应用…

矩阵乘法之叉乘和点乘

矩阵的乘法包含两种:点乘和叉乘。 矩阵点乘的含义是对应元素相乘,例如矩阵,同样存在矩阵, 那么. 矩阵叉乘含义与我们平时理解矩阵相乘一致,即一个矩阵A,若要与另外一个矩阵相乘,另另外一个矩阵的行数必须…

【iOS】NSOperation,NSOperationQueue

文章目录 前言概念使用NSOperation,NSoperationQueue的好处操作和操作队列操作(Operation)操作队列(Operation Queues) NSOperation,NSOperationQueue常用属性和方法归纳NSOperation常用属性和方法NSOperat…

阶乘求和,求 1 + 2 + 3 + ... + 202320232023 ,阶乘总和的末尾九位数字

求 1! 2! 3! … 202320232023! ,总和的末尾九位数字。 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅是基础那么简单…… 地址:ht…

learn_C_deep_8 (循环语法的理解、void的用法以及理解)

目录 循环语法的理解 break关键字 continue关键字 continue跳转的位置 goto关键字 void的用法以及理解 void是否可以定义变量 为何 void 不能定义变量 void的应用场景 void 指针 循环语法的理解 for循环是一种常用的循环结构,它适合于在已知循环次数的情况…

ChatGPT prompt engineering (中文版)笔记 |吴恩达ChatGPT 提示工程

目录 一、资料二、 指南环境配置两个基本原则&#xff08;最重要!!!!&#xff09;原则一&#xff1a;编写清晰、具体的指令**策略一&#xff1a;使用分隔符清晰地表示输入的不同部分**&#xff0c;**分隔符可以是&#xff1a;&#xff0c;""&#xff0c;<>&…

浅谈几个通信概念-如何理解卷积,负频率,傅里叶变换,奈奎斯特采样定理?

1.如何理解卷积&#xff1f; t时刻的输出信号是t时刻之前的无数小的脉冲序列冲击引起的。 2. 如何理解欧拉公式&#xff0c;复指数信号呢&#xff1f; 可以看成一个点在复平面上以角速度w进行逆时针的旋转。 傅里叶分析&#xff1a; 整体到部分&#xff0c;把一个信号分解成无…

【网络】socket套接字基础知识

文章目录 IP与端口号TCP/UDP协议网络字节流socket套接字接口总结 IP与端口号 IP 每台主机都有自己的IP地址&#xff0c;所以当数据从一台主机传输到另一台主机就需要IP地址。报头中就会包含源IP和目的IP 源IP地址&#xff1a;发送数据报那个主机的IP地址&#xff0c;目的IP地…

JMeter开发web及手机APP自动化脚本练习

一、打开浏览器代理服务器设置 我这里用的是360浏览器&#xff0c;打开浏览器代理服务器设置&#xff0c;端口要与jmeter中的端口设置保持一致哦。 二、JMeter设置代理 JMeter设置代理&#xff08;jmeter中的端口要与360浏览器端口设置保持一致哦。&#xff09; 三、启动代理运…

BM17 二分查找-I

二分查找-I_牛客题霸_牛客网 (nowcoder.com) 设置中间值mid 每次判断目标值和中间值的大小 缩短区间 直到区间全被搜索完成 class Solution { public: /** * 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可 * * * param nums …

因子挖掘框架cs优缺点介绍和使用说明

cs框架的优点和缺点 优点和ts一样,就是速度非常快缺点有好几个:必须使用根据过去一定天数计算因子值,持有一定天数之后再平衡的模式;必须使用连续的数据,如果是期货期权等需要合成连续数据。资金不足的时候不会拒单。cs框架使用方法 设计理念 计算因子由用户进行计算,因…

Yolov7论文详解

论文地址&#xff1a;https://arxiv.org/pdf/2207.02696.pdfhttps://arxiv.org/pdf/2207.02696.pdf 项目地址&#xff1a; WongKinYiu/yolov7: Implementation of paper - YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors (gith…

多进程多线程并发服务器代码实现

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

R语言:鉴于计算10亿以内训练模型记录for循环的加速

文章目录 1 前言2 几个循环2.1 100以内的和2.2 100以内奇数和/偶数和 3 多重循环3.1 向量化3.2 合并循环3.3 apply函数3.4 矩阵运算3.5 foreach分解任务 4 讨论 1 前言 笔者主力机是MBAM1芯片&#xff08;8256&#xff09;&#xff0c;某个下午巩固循环突然思考到个问题&#…

【Luenberger Observer】龙贝格观测器及示例Matlab仿真

目录 龙贝格观测器 龙贝格观测器示例和仿真 Matlab仿真 龙贝格观测器 观测器&#xff1a;根据系统的输入u和输出y估计系统的状态x。 SISO系统的状态空间方程如下 龙贝格观测器&#xff0c;通过在原系统添加基于输出误差校正项&#xff0c;构造状态空间方程&#xff0c;设x_h…