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

news/2024/10/19 0:21:41/
(现实是此岸,梦想是彼岸,中间隔着湍急的河流,行动则是架在河上的桥梁。——克雷洛夫)

在这里插入图片描述

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);elseconsole.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 mongodbconst getUser = async function () { dbCollection.get };const getMobile = async function  () { dbCollection.get };const getAddress = async function () { dbCollection.get };const getSex = async function () { dbCollection.get };const getHeight = async function () { dbCollection.get };const getWeight = async function () { 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; // 定义agesetInterval(function () {this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局console.log(this.age);}, 1000)}
new Persion();// 那么为了解决this问题,就可以使用提前赋值this的方式const Persion2 = function () { //定义构造函数this.age = 0; // 定义ageconst that = this;setInterval(function () {that.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局console.log(that.age);}, 1000)}
new Persion2();// 也可以使用bind的方式
const Persion3 = function () { //定义构造函数this.age = 0; // 定义agesetInterval(function () {this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局console.log(this.age);}.bind(this), 1000);
}
new Persion3();

http://www.ppmy.cn/news/63290.html

相关文章

P9299 [CCC 2023 J1] Deliv-e-droid

题目描述 In the game, Deliv-e-droid, a robot droid has to deliver packages while avoiding obstacles. At the end of the game, the final score is calculated based on the following point system: Gain 5050 points for every package delivered.Lose 1010 points…

操作系统:中断

目录 什么是中断什么是软中断系统里有哪些软中断?如何定位软中断 CPU 使用率过高的问题?参考资料 什么是中断 中断是操作系统用来响应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的程序,然后调…

链路,设备,带宽冗余技术

链路冗余: 一.设计冗余的目的——提高可靠性(通信时一条路不通走另一条路即冗余链路) 采用具有冗余的核心层,分布层和接入层,试图消除网络中的单点故障二.实施冗余的注意事项 1.MAC数据库不稳定—MAC地址表中的内容不…

思享工具箱,各种工具汇总

站长,开发者常用在线工具集合 - 思享工具箱 思享工具箱,各种工具汇总 https://tool.4xseo.com/ JSON工具 Json格式化 Json格式化(上下) Json格式化(左右) Json在线压缩转义 Json生成C#实体类 Json生成Java实体类 Json生成Go结构…

达梦:dts工具迁移mysql decimal(65,30)的字段,报精度超出定义

本文旨在分享迁移MySQL decimal字段​​​​​​​时遇到“精度超出定义”问题时,如何理解MySQL和达梦对于decimal 等这一类数值数据类型。 1.了解达梦的数值数据类型定义 ​​​​​​​​​​​​​​NUMERIC 类型 语法:NUMERIC[(精度 [, 标度])]功…

IO-BIO概述

介绍 ​ BIO就是: blocking IO,同步阻塞IO。应用程序向操作系统请求网络IO操作,这时应用程序会一直等待;另一方面,操作系统收到请求后,也会等待,直到网络上有数据传到监听端口;操作系统在收集数…

简单实现基于UDP与TCP的回显服务器

目录 前言UDP 版的回显服务器需要用到的 api服务端客户端UDP 版本的字典客户端和字典服务器 TCP 版的回显服务器需要用到的 api服务器客户端对服务器进行改进(使用线程池)TCP 版本的字典客户端和字典服务器 前言 我们写网络程序, 主要编写的是应用层代码. 真正要发送这个数据,…

华为OD机试真题-24点运算【2023】【JAVA】

一、题目描述 计算24点是一种扑克牌益智游戏,随机抽出4张扑克牌,通过加(+),减(-),乘(*), 除(/)四种运算法则计算得到整数24,本问题中,扑克牌通过如下字符或者字符串表示,其中,小写joker表示小王,大写JOKER表示大王:3 4 5 6 7 8 9 10 J Q K A 2 joker JOKER 本程序要…