call、apply、bind 作用和区别

目录

一、用法

1.1、call用法

1.2、apply用法 

1.3、bind用法

二、区别

2.1、相同点 

2.2、不同点

三、使用场景 

3.1 apply()的使用合并两个数组

3.1.1 原理

3.1.2 如何解决参数过多的问题呢?—— 将参数数组切块,循环传入目标方法

3.2 apply()、call() 获取数组中的最大值和最小值

3.3 call的使用 Object.prototype.toString() 验证是否是数组

3.4 类数组对象(Array-like Object)使用数组方法 

3.4.1 什么是类数组对象?为什么要出现类数组对象?

3.4.2 使用 Array.prototype.slice.call 将类数组对象转换为数组 

3.4.3 使用 ES6 提供的 Array.form / 解构赋值实现类数组对象转数组

3.5 call()调用父构造函数实现继承

3.6. bind() 使用场景


一、用法

call、apply、bind 都是函数 Function 原型上的方法,三者的功能都是用来改变函数中的 this 指向

 const ajie = {name: '阿杰'}const xiaoyu = {name: '小雨'}function hi(msg, msg2) {console.log(msg + msg2 + this.name);}hi.call(ajie, '你好啊', 'aa');//你好啊aa阿杰hi.apply(ajie, ['你好啊', 'yy']); // 你好啊yy阿杰hi.bind(xiaoyu, '哈哈', '原来是你啊')();//哈哈原来是你啊小雨//一般这样使用bind,把得到的函数保存下来const hixiaoyu = hi.bind(xiaoyu)hixiaoyu('哈哈', '终于等到你');//哈哈终于等到你小雨let o = {nick: '华晨',hi() {console.log(this.nick); }}setTimeout(callback, 0); //此时运行会报错,因为当执行this.nick时cb前面没有.对象,this指向window//正确写法,要通过bind()改变this的指向setTimeout(o.hi.bind(o), 0); function setTimeout(callback, ms) {cb();}//经常有如下业务var nickname = "Kitty";
function Person(name){this.nickname = name;this.distractedGreeting = function() {setTimeout(function(){console.log("Hello, my name is " + this.nickname);}, 500);}
}var person = new Person('jawil');
person.distractedGreeting();
//Hello, my name is Kitty
这里输出的nickname是全局的,并不是我们创建 person 时传入的参数,因为 setTimeout 在全局环境中执行,所以 this 指向的是window。这边把 setTimeout 换成异步回调也是一样的,比如接口请求回调。解决方案有下面两种。解决方案1:缓存 this值
var nickname = "Kitty";
function Person(name){this.nickname = name;this.distractedGreeting = function() {var self = this; // addedsetTimeout(function(){console.log("Hello, my name is " + self.nickname); // changed}, 500);}
}var person = new Person('jawil');
person.distractedGreeting();
// Hello, my name is jawil解决方案2:使用 bindvar nickname = "Kitty";
function Person(name){this.nickname = name;this.distractedGreeting = function() {setTimeout(function(){console.log("Hello, my name is " + this.nickname);}.bind(this), 500);}
}var person = new Person('jawil');
person.distractedGreeting();
// Hello, my name is jawil

1.1、call用法

call() 方法是预定义的 JavaScript 方法,它可以用来调用所有者对象作为参数的方法。通过 call(),您能够使用属于另一个对象的方法。

案列:

  const Person = {fullName: function () {return this.firstName + this.lastName}}const Person2 = {firstName: "哈",lastName: "嘿嘿",}
// 哈嘿嘿 此时fullName函数中this指向newPerson,通过call()来改变其中this的指向Person.fullName.call(Person2) 

 传参:

call传入的参数数量不固定,第一个参数代表函数内的this指向,从第二个参数开始往后,每个参数被依次传入函数。

  const Person1 = {fullName: function (country, city) {return this.firstName + this.lastName + " " + country + " " + city}}const Person2 = {firstName: "张",lastName: "三",}Person1.fullName.call(Person2, '中国', '河南') // 张三 中国 河南 

call是包装在apply上面的一颗语法糖,如果我们既明确知道函数接受参数的个数,又想清晰明了的表达形参和实参的对应关系,那么可以用call来传达参数。

1.2、apply用法 

apply接受两个参数,第一个参数指定了函数体内的this指向。第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数。

案列:

  const person = {fullName: function (country, city) {return this.firstName + this.lastName + " " + country + " " + city}}const newPerson = {firstName: "壮",lastName: "志国",}person.fullName.apply(newPerson, ['中国', '河南']) // 壮志国 中国 河南

代码中参数 ['中国', '河南'] 被放在数组中一起传给了person的fullName函数,分别对应fullName函数中的country,city参数。

当调用一个函数时,js的解释器并不会计较形参和实参在数量,类型以及顺序上的区别,js在内部就是用一个数组来表示的。从这个意义上来说,call比apply使用率更高,我们不必关心多少参数被传入函数,只要用apply一股脑推进去就行了。

1.3、bind用法


相信大家在使用React调用函数的时候必须使用bind(this),后直接在class中声明函数即可正常使用,但是为什么要使用这个呢?

bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:f.bind(obj),实际上可以理解为obj.f()这时f函数体内的this自然指向的是obj

  const person = {fullName: function (country, city) {return this.firstName + this.lastName + " " + country + " " + city}}const newPerson = {firstName: "壮",lastName: "志国",}// 打印出fullName函数person.fullName.bind(newPerson, '中国', '河南')() // 壮志国 中国 河南

bind传参和call是一致的,内部实现是先把当前函数保存起来,然后返回一个新函数,当我们将来要执行当前函数时,实际返回的是刚刚返回的新的fullName函数。它不会立即执行,而是需要的时候调用即可。

二、区别


2.1、相同点 


bind、call、apply都是用来指定一个函数内部的this的值。 
接收的第一个参数都是this要指向的对象。
都可以利用后续参数传参。


2.2、不同点


call和bind传参相同,多个参数依次传入的。
apply只有两个参数,第二个参数为数组。
call和apply都是对函数进行直接调用,而bind方法不会立即调用函数,而是返回一个修改this后的函数。

三、使用场景 


call函数的使用多用于类的继承。
apply函数可配合Math.max()用于计算数组最大值等。
bind函数可用于函数内部有定时器,改变定时器内部的this指向

3.1 apply()的使用合并两个数组


3.1.1 原理


使用 apply 将 Array.prototype.push 这个函数方法的 this 指向改成 arr1
也就是说:arr1 现在有一个 push 属性方法
又因为 apply 改变 this 指向后,会直接执行函数
所以 arr1 会直接调用 push 方法,并接收 arr2 传来的参数数组
最终实现数组合并
注意:

arr2 数组不能太大,因为一个函数能接受的参数个数有限,JavaScript 核心限制在 65535
不同引擎限制不同,如果参数太多,可能会报错,也可能不会报错但参数丢失


3.1.2 如何解决参数过多的问题呢?—— 将参数数组切块,循环传入目标方法


具体实现步骤:

定义每次连接的数组,最多有 groupNum 个元素
需要连接的数组 arr2 总长度设为 len
使用 for 循环,每循环一次,i 增加一个分组那么多
也就是说,每循环一次,就连接原数组 和 新数组的第 i 个分组
最后一个分组,如果元素不够,则直接截取到最后,也就是 arr2.length

function concatOfArray(arr1, arr2) {// 数组分组后,每组元素个数var groupNum = 32768;var len = arr2.length;// 每循环一次,数组都添加一组个数for (var i = 0; i < len; i += groupNum) {// 当最后一组个数不足 groupNum 时,直接截取到最后即可,也就是 len// 一块一块连接数组Array.prototype.push.apply(arr1, arr2.slice(i, Math.min(i + groupNum, len)));}return arr1;
}// 验证代码
var arr1 = [-3, -2, -1];
var arr2 = [];
for (var i = 0; i < 1000000; i++) {arr2.push(i);
}Array.prototype.push.apply(arr1, arr2);
// Uncaught RangeError: Maximum call stack size exceededconcatOfArray(arr1, arr2);
// (1000003) [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]


3.2 apply()、call() 获取数组中的最大值和最小值


数组没有直接获取最大最小值的方法,但是 Math 有
使用 call 将 Max.max 这个方法的 this 指向绑定到 Math 上
由于 call 会让绑定后的函数立刻执行,因此接收到数组后,Math 会立即执行寻找最值
 

var numbers = [5, 458 , 120 , -215 ]; Math.max.apply(Math, numbers); // 458    Math.max.call(Math, 5, 458 , 120 , -215); // 458// ES6
Math.max.call(Math, ...numbers); // 458


3.3 call的使用 Object.prototype.toString() 验证是否是数组


不同对象的 toString() 有不同的实现,可以通过 Object.prototype.toString() 获取每个对象的类型使用 call()、apply() 实现检测,下面是我在 chrome 中打印的效果

因此,可以这么封装:

function isArray(obj){ return Object.prototype.toString.call(obj) === '[object Array]';
}isArray([1, 2, 3]); // true


3.4 类数组对象(Array-like Object)使用数组方法 


3.4.1 什么是类数组对象?为什么要出现类数组对象?


JavaScript 中有一种对象,结构非常像数组,但其实是个对象:

类数组对象不具有:push、shift、forEach、indexOf 等数组方法
类数组对象具有:指向对象元素的数字索引下标 和 length 属性
常见的类数组对象:

arguments 参数列表
DOM API 返回的 NodeList
类数组对象出现的原因:为了更快的操作复杂数据。

JavaScript 类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。Array存储的对象能动态增多和减少,并且可以存储任何 JavaScript 值。JavaScript 引擎会做一些内部优化,以便对数组的操作可以很快。然而,随着 Web 应用程序变得越来越强大,尤其一些新增加的功能例如:音频视频编辑,访问 WebSockets 的原始数据等,很明显有些时候如果使用 JavaScript 代码可以快速方便地通过类型化数组来操作原始的二进制数据,这将会非常有帮助。

3.4.2 使用 Array.prototype.slice.call 将类数组对象转换为数组 


slice 将 Array-like 类数组对象,通过下标操作,放进了新的 Array 里面:

将数组的 slice 方法,通过 call 改变 this 指向,绑定到需要修改的类数组对象;
由于 call 会在修改完绑定后自动执行函数,因此 类数组对象 调用它被绑的 slice 方法,并返回了真的数组
// 类数组对象不是数组,不能使用数组方法
var domNodes = document.getElementsByTagName("*");
domNodes.unshift("h1");
// TypeError: domNodes.unshift is not a function
 
// 使用 Array.prototype.slice.call 将类数组对象转换成数组
var domNodeArrays = Array.prototype.slice.call(domNodes);
domNodeArrays.unshift("h1");
// ["h1", html.gr__hujiang_com, head, meta, ...] 
也可以这么写,简单点 —— var arr = [].slice.call(arguments);

注意:此方法存在兼容性问题,在 低版本IE(< 9) 下,不支持 Array.prototype.slice.call(args),因为低版本IE下的 DOM 对象,是以 com 对象的形式实现的,JavaScript 对象与 com 对象不能进行转换

3.4.3 使用 ES6 提供的 Array.form / 解构赋值实现类数组对象转数组


Array.from() 可以将两种 类对象 转为 真正的数组:

类数组对象(arguments、NodeList)
可遍历(iterable)对象(包括 ES6 新增的数据结构 Set 和 Map)
let arr = Array.from(arguments);
let arr = [...arguments];


3.5 call()调用父构造函数实现继承


在子构造函数中,通过调用父构造函数的 call()方法,实现继承

SubType 的每个实例都会将SuperType 中的 属性/方法 复制一份

function  SuperType(){
    this.color=["red", "green", "blue"];
}
function  SubType(){
    // 核心代码,继承自SuperType
    SuperType.call(this);
}
 
var instance1 = new SubType();
instance1.color.push("black");
console.log(instance1.color);
// ["red", "green", "blue", "black"]
 
var instance2 = new SubType();
console.log(instance2.color);
// ["red", "green", "blue"]

缺点:

只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现复用,每个子类都有父类实例函数的副本,影响性能 


3.6. bind() 使用场景

可以通过toString() 来获取每个对象的类型,但是不同对象的 toString()有不同的实现,所以通过 Object.prototype.toString() 来检测,需要以 call() / apply() 的形式来调用,传递要检查的对象作为第一个参数。

function isArray(obj){ return Object.prototype.toString.call(obj) === '[object Array]';
}
function isNumber(obj) {return Object.prototype.toString.call(obj) === '[object Number]';
}function isString(obj) {return Object.prototype.toString.call(obj) === '[object String]';
}
isArray([1, 2, 3]);
// true// 直接使用 toString()
[1, 2, 3].toString();  // "1,2,3"
"123".toString();   // "123"
123.toString();   // SyntaxError: Invalid or unexpected token
Number(123).toString(); // "123"
Object(123).toString(); // "123"

  另一个验证是否是数组的方法,这个方案的优点是可以直接使用改造后的 toStr。 

var toStr = Function.prototype.call.bind(Object.prototype.toString);
function isArray(obj){ return toStr(obj) === '[object Array]';
}
isArray([1, 2, 3]);
// true// 使用改造后的 toStr
toStr([1, 2, 3]);  // "[object Array]"
toStr("123");   // "[object String]"
toStr(123);   // "[object Number]"
toStr(Object(123)); // "[object Number]"

上面方法首先使用 Function.prototype.call函数指定一个 this 值,然后 .bind 返回一个新函数,始终将 Object.prototype.toString 设置为传入参数。其实等价于 Object.prototype.toString.call() 。

这里有一个前提toString()方法没有被覆盖

Object.prototype.toString = function() {return '';
}
isArray([1, 2, 3]);
// false

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

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

相关文章

【C++风云录】探索食品工艺的新工具:创新你的食谱

食品加工模拟&#xff1a;优化你的营养研究 前言 在科技的推动下&#xff0c;食品科学领域已经逐步引入了智能化工具&#xff0c;以协助研究人员和工业界进行更精细、更深入的研究。本文将详细介绍六款与食品科学紧密相关的软件和库&#xff0c;它们包括FoodCAD, Nutritional…

数据结构十三:八大排序算法

排序算法&#xff08;sorting algorithm&#xff09;是用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用&#xff0c;因为有序数据通常能够被更高效地查找、分析和处理。排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求设定&am…

树(Tree)和二叉树

1.树的定义 树是一种非线性的数据结构&#xff0c;它表现的关系是一对多 它是由n&#xff08;n>0&#xff09;个结点组成的有限集&#xff0c;当n 0时&#xff0c;称为空树。 在任意一棵非空树中应满足&#xff1a; 1.有且仅有一个特殊的根节点&#xff0c;根节点没有前…

【江科大STM32学习笔记】GPIO输出

一、GPIO简介 1.GPIO&#xff08;General Purpose Input/Output&#xff09;通用输入输出 2.可配置为8种输入输出模式 3.引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 部分引脚输入可为5V但输出只能是3.3V 4.输出模式下可控制端口输出高低电平&#xff0c;用…

Python基础详解四

一&#xff0c;Json解析 字典转换为JSON&#xff1a; import jsondata [{"name":"袁震","age":20},{"name":"张三","age":21},{"name":"李四","age":22}] str json.dumps(data) …

Linux(centos7)系统配置 ntpd服务设置时间同步

一 、应用场景 两台服务器,要求使他们时间同步,有人问为什么要时间同步?如果一个集群中,时间相差很大,那么会出现很多诡异的问题,你也不想在一个无法解决的问题上浪费几天时间吧!总之,设置服务器之间时间同步,为了避免很多问题的发生! ntpd(Network Time Protocol …

【第20章】spring-mvc之定时任务

文章目录 前言一、开启1. 打开开关2. 定时任务类3. 执行结果 二、定时任务线程池1.定义线程池2.开启异步3. 定时任务类4. 执行结果 三、cron总结 前言 定时任务是项目中比较常见的功能&#xff0c;常用于定时发送消息、拉取数据、数据备份等&#xff1b; 为什么要放到SpringM…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (四)

基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;四&#xff09; 大家继续看 https://lilianweng.github.io/posts/2023-06-23-agent/的文档内容 第三部分&#xff1a;工具使用 工具的使用是人类的一个显着而显着的特征。我们创造、修改和利用外部物体来完成超…

开发一款抓大鹅游戏

你抓大鹅第二关过了吗&#xff1f;近期,经常在身边听见这样的疑问。作为本月的爆款游戏&#xff0c;抓大鹅以简单的消消乐玩法、动感的音乐、易上手的操作方式、简单易懂的游戏规则吸引了大量玩家。就像2022年的羊了个羊&#xff0c;2021年的合成大西瓜&#xff0c;但它们确实非…

20240511,谓词,内建函数对象

拜托铠甲勇士真的帅好不好&#xff01;&#xff01;&#xff01; STL案例2-员工分组 10个员工&#xff0c;指派部门&#xff0c;员工信息&#xff08;姓名&#xff0c;工资组成&#xff0c;部门&#xff1a;策划&#xff0c;美术&#xff0c;研发&#xff09;&#xff0c;随机…

【gpedit.msc】组策略编辑器的安装,针对windows家庭版,没有此功能

创建一个记事本文件然后放入以下内容 echo offpushd "%~dp0"dir /b %systemroot%\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >gp.txtdir /b %systemroot%\servicing\Packages\Microsoft-Windows-GroupPolicy-…

[框架] Unity 公共执行器

本篇我们通过使用单例模式来创建一个公共执行器&#xff0c;使得原本应该在Update()、FixedUpdate()中的指令都可以统一放在一个对象中执行&#xff0c;且可进行添加和移除操作。 1. 创建单例模式改造器&#xff1a;SingletonMono 我们先创建一个单例模式改造器&#xff0c;使…

2024数维杯

截至我所知的信息&#xff08;2023年&#xff09;&#xff0c;“数维杯”并不是一个广泛认知的赛事名称&#xff0c;至少在主流的学术、教育或科技竞赛领域中没有明确对应的赛事。这可能是指某个特定领域或地区的竞赛&#xff0c;也可能是一个新兴的比赛或者非正式的称谓。 如…

SSM【Spring SpringMVC Mybatis】——Mybatis

目录 1、初识Mybatis 1.1Mybatis简介 1.2 官网地址 2、搭建Mybatis框架 2.1 准备 2.2 搭建Mybatis框架步骤 1. 导入jar包 2. 编写核心配置文件【mybatis-config.xml】 3. 书写相关接口及映射文件 4. 测试【SqlSession】 2.3 添加Log4j日志框架 导入jar包 编写配置文…

ERA5数据的区别

ERA5 hourly data on single levels from 1940 to present 链接 ERA5是欧洲中期天气预报中心(ECMWF)的第五代全球气候和天气再分析产品&#xff0c;涵盖过去80年的数据。数据可从1940年开始获取&#xff0c;ERA5取代了ERA-Interim再分析产品。 再分析将全球范围内的模型数据与…

详解drop,delete,truncate区别

在SQL中&#xff0c;"DROP"、"DELETE"和"TRUNCATE"是用于删除数据的不同命令&#xff0c;它们之间有一些重要的区别&#xff1a; DROP&#xff1a; DROP用于删除数据库对象&#xff0c;例如删除表、视图、索引、触发器等。使用DROP删除的对象将…

27.哀家要长脑子了!---栈与队列

1.739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; 用单调栈的方法做&#xff1a; 从左到右遍历数组&#xff1a; 栈中存放的是下标&#xff0c;每个温度在原数组中的下标&#xff0c;从大到小排列&#xff0c;因为这样才能确保的是最近一天的升高温度 如果栈为空&am…

Vue面试经验2

Vue 你说你在vue项目中实现了自定义指令&#xff0c;如何实现 全局指令在main.js入口文件中实现 使用方法&#xff1a;v-指令名称 每个钩子函数都有两个参数&#xff08;ele,obj&#xff09; ele:绑定指令的元素 obj:指令的一些信息&#xff08;比如绑定指令的值&#xff0c…

速盾:如何选择适合自己的网络安全解决方案?

选择适合自己的网络安全解决方案是非常重要的&#xff0c;因为网络安全问题涉及到个人隐私和重要数据的保护。在选择网络安全解决方案时&#xff0c;需要考虑以下几个方面&#xff1a; 网络规模和需求&#xff1a;首先要了解自己的网络规模和需求&#xff0c;包括网络设备的数量…

虚表,虚函数习题

6. 关于虚表说法正确的是&#xff08;d &#xff09; A&#xff1a;一个类只能有一张虚表 多重继承 B&#xff1a;基类中有虚函数&#xff0c;如果子类中没有重写基类的虚函数&#xff0c;此时子类与基类共用同一张虚表 即使子类重写了基类的虚函数&#xff0c;此时子类与…