【JavaScript】Function的祖传方法call与apply

2023-11-17

在这里插入图片描述

引言

内容速递

看了本文您能了解到的知识!

在本篇文章中,将带你了解什么是call和applycall和apply的用途、如何手写callapply以及callapply的使用场景。

1、什么是call和apply

call()apply()JavaScript中的两个内置方法,用于调用函数并指定函数中的this值。

两者的区别是:call()方法的语法和作用与apply()方法类似,只有一个区别,就是call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组

1.1、call

MDN给的解释:Function.prototype.call()

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

1.2、apply

MDN给的解释:Function.prototype.apply()

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

需要注意的是,使用callapply方法改变函数的this指向后,函数会立即执行,并返回执行结果。

2、call和apply的语法介绍

2.1、call语法

语法:

function.call(thisArg, arg1, arg2, ...)

参数:

  • thisArg:可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

  • arg1, arg2, …:指定的参数列表。

返回值:

使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

2.1、apply语法

语法:

function.apply(thisArg, [argsArray])

参数:

  • thisArg:在 func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

  • argsArray: 可选。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。

返回值:

调用有指定 this 值和参数的函数的结果。

3、call与apply的用法

3.1、call的用法

一个函数继承另外一个函数的属性

代码:

function Guizimo(name, age) {
  this.name = name
  this.age = age
}

function Zimo(name, age) {
  Guizimo.call(this, name, age)
  this.sex = 'man'
}

console.log(new Zimo('guizimo', '24'))
// Zimo{name: 'guizimo', age: '24', sex: 'man'}

代码解析:

  1. Guizimo中定义了nameage两个属性。

  2. Zimo中使用call,让Zimo继承了Guizimo的属性

  3. new一个Zimo,拥有Guizimo中定义的属性。

3.2、apply的用法

合并两个数组

代码:

const array = ['a', 'b'];
const elements = [0, 1, 2];

array.push.apply(array, elements);

console.log(array);
// ["a", "b", 0, 1, 2]

代码解析:

  1. 定义了两个数组arrayelements

  2. arraypush上使用apply,将elements数组传入。

  3. array拥有elements的元素。

4、手写call与apply

4.1、手写call

思路:

  1. 处理好传入的this和参数。

  2. 创建一个临时属性接收当前this

  3. 将参数作为属性置入创建的的临时属性中,并执行得到结果。

  4. 清理掉刚创建的临时属性。

  5. 返回结果

普通版

/**
 * 自定义实现call
 * @returns {*|void}
 */
Function.prototype.myCall = function () {
  // 判断this的指向
  if (typeof this !== 'function') {
    throw new Error('type error')
  }
  // 处理参数,拿到传入的this与参数
  const args = Array.from(arguments)
  // 如果传入的this为null时,指向全局的window对象
  const newThis = args.shift() || window
  // 创建一个临时属性接受this,注意唯一性,可能会覆盖原有的属性
  newThis.fn = this
  // 将参数作为属性置入
  const res = newThis.fn(...args)
  // 删除属性,防止污染
  delete newThis.fn
  // 返回
  return res
}

进阶

想了一下这个还是可以改进的。在使用newThis.fn = this的时候,这个fn需要保持唯一性,不覆盖外部传入的属性,因此可以使用Symbol,注意使用Symbol之后就不再使用newThis.fn,而是newThis[fn]

进阶版

/**
 * 自定义实现call,优化版
 * @returns {*}
 */
Function.prototype.myCallPlus = function () {
  // 判断this的指向
  if (typeof this !== 'function') {
    throw new Error('type error')
  }
  // 处理参数,拿到传入的this与参数
  const args = Array.from(arguments)
  // 如果传入的this为null时,指向全局的window对象
  const newThis = args.shift() || window
  // 创建一个唯一的key
  let fn = Symbol()
  // 接收临时this
  newThis[fn] = this
  // 将参数作为属性置入
  const res = newThis[fn](...args)
  // 删除属性,防止污染
  delete newThis[fn]
  // 返回
  return res
}

4.2、手写apply

思路:

call几乎一样,区别是在参数的处理

  1. 处理好传入的this和参数。
  2. 创建一个临时属性接收当前this
  3. 将参数作为属性置入创建的的临时属性中,并执行得到结果。
  4. 清理掉刚创建的临时属性。
  5. 返回结果。

普通版

/**
 * 自定义实现apply
 * @param thisArg
 * @param args
 * @returns {*|void}
 */
Function.prototype.myApply = function (thisArg, args) {
  // 判断this的指向
  if (typeof this !== 'function') {
    throw new Error('type error')
  }
  // 处理外部传递过来的this,如果this为null时,指向全局的window对象
  const newThis = thisArg || window
  // 创建一个Null对象,用作返回的载体
  let res = null
  // 创建一个临时属性接受this,注意唯一性,可能会覆盖原有的属性
  newThis.fn = this
  // 将参数作为属性置入
  res = newThis.fn(...args)
  // 删除属性,防止污染
  delete newThis.fn
  // 返回
  return res
}

进阶版

/**
 * 自定义实现apply,进阶版
 * @param thisArg
 * @param args
 * @returns {*}
 */
Function.prototype.myApplyPlus = function (thisArg, args) {
  // 判断this的指向
  if (typeof this !== 'function') {
    throw new Error('type error')
  }
  // 处理外部传递过来的this,如果this为null时,指向全局的window对象
  const newThis = thisArg || window
  // 创建一个唯一的key
  const fn = Symbol()
  // 接收临时this
  newThis[fn] = this
  // 传入参数,调用新属性
  const res = newThis[fn](...args)
  // 删除属性,防止污染
  delete newThis[fn]
  // 返回
  return res
}

5、使用场景

5.1 call的使用场景

1、调用父构造函数

在子构造函数中可以使用call调用父构造函数,这种方式可以实现继承,这种使用子构造方法之后,都会拥有父构造函数的属性。

// 父构造方法
function Father (name, age) {
  this.name = name
  this.age = age
}

// 子构造方法
function Child(name, age) {
  Father.call(this, name, age)
  this.sex = 'man'
}

console.log(new Child('guizimo', '24'))
// Child {name: 'guizimo', age: '24', sex: 'man'}

2、调用匿名函数

创建了一个匿名函数,通过callobj作为属性给到匿名函数使用。

const obj = {
  name: 'guizimo',
  age: '24'
};  // 此处;不可省略

(function (id) {
  this.print = function () {
    console.log(`id : ${id} ${this.name} : ${this.age}`);
  };
  this.print();
}).call(obj, 1);

// id : 1 guizimo : 24

这里我遇到了一个小插曲:

有一个报错:Uncaught TypeError: {(intermediate value)(intermediate value)} is not a function

原因是在匿名函数使用之前,需要加上;

3、设置指定的上下文this

给某个函数指定上下文this,这是call比较通常的用法

const obj = {
  name: 'guizimo',
  description: 'a good boy'
};

function fn() {
  let reply = [this.name, 'is', this.description].join(' ');
  console.log(reply);
}

fn.call(obj);
// guizimo is a good boy

4、使用call不携带this参数

在使用call的时候不给到指定的this参数,这时this的值为全局对象

var name = 'guizimo'  // 注意这里使用 var 来声明

function fn() {
  console.log(`${this.name} is a good boy`)
}

fn.call()
// guizimo is a good boy

注意

在严格模式下,this的值将会是undefined。在调用call的时候,会给到报错。

5.2、apply的使用场景

1合并数组

使用apply可以将push改造为类似concat的效果,但是不会创建一个新的数组,而是在原数组上直接合并。

const array = ['a', 'b'];
const elements = [0, 1, 2];

array.push.myApply(array, elements);

console.log(array);
// ["a", "b", 0, 1, 2]

2、兼容max和min的限制

apply有一个好处就是可以避免循环。

const numbers = [5, 6, 2, 3, 7];

let max = Math.max.apply(null, numbers); // 等同与 Math.max(5, 6, 2, 3, 7)
let min = Math.min.apply(null, numbers); // 等同与 Math.min(5, 6, 2, 3, 7)

看起来使用了apply的收益还是不大,当我们使用Math.max和Math.min是会出现一个问题

JavaScript引擎参数长度上限: 65536)。

为了避免这个问题,可以将数组切块后循环传入目标方法。

function minOfArray(arr) {
  let min = Infinity;
  let QUANTUM = 32768;

  for (let i = 0, len = arr.length; i < len; i += QUANTUM) {
    const submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
    min = Math.min(submin, min);
  }

  return min;
}

let min = minOfArray([5, 6, 2, 3, 7]);

博客说明与致谢

文章所涉及的部分资料来自互联网整理,其中包含自己个人的总结和看法,分享的目的在于共建社区和巩固自己。

引用的资料如有侵权,请联系本人删除!

感谢勤劳的自己个人博客GitHub,公众号【归子莫】,小程序【子莫说】

如果你感觉对你有帮助的话,不妨给我点赞鼓励一下,好文记得收藏哟!

幸好我在,感谢你来!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【JavaScript】Function的祖传方法call与apply 的相关文章

  • Javascript - 模板字符串不能漂亮地打印对象

    我可以使用 ES6 模板字符串来漂亮地打印 javascript 对象吗 这是来自 React Native 项目 其中console log 输出到 Chrome 调试工具 我想要的是 const description App open
  • 为什么可以从 console.log 访问 JavaScript 私有方法

    我写了一个简单的代码 const secure new class privateProperty 4 privateMethod console log The property this privateProperty should n
  • Android 上的 Chrome 强制隐藏地址栏

    我最近开发了一个获取混合 http https 内容的网站 因此 我总是将地址栏显示在顶部 它不会像其他网站那样自动隐藏 这就是我要说的 This https planetkde org 是网站的链接 内容是从各种来源获取的 因此无法过滤非
  • 页面其余部分完成加载后延迟加载 html5 视频

    我有一个视频元素用作我正在构建的页面底部部分的背景 我试图通过将 src 存储为 data src 属性并使用 jQuery 在其他资源加载后将其应用到 src 属性 因为它不是英雄图像或任何东西 我想加载海报以节省加载时间 然后稍后加载视
  • History.pushState和页面刷新

    我开始研究 HTML5 新历史 API 不过 我有一个问题 如何处理页面刷新 例如 用户单击一个链接 该链接由 js 函数处理 该函数 异步加载页面内容 使用history pushState 更改URL 用户刷新页面 但是服务器上当然不存
  • 使用 Jquery 更改 css 属性时的事件检测

    有没有办法检测元素的 显示 css 属性是否更改 是否更改为无 块或内联块 如果没有的话有什么插件吗 谢谢 Note 突变事件 https developer mozilla org en US docs Web Guide Events
  • WaveSurfer JS 无法在 Firefox 中为特定的 mp3 音频文件生成图表

    我们面临着在 Firefox 中使用 wavesurfer JS 对某些特定格式的 mp3 文件绘制音频可视化 图表 的问题 它总是给我们这样的错误 传递给decodeAudioData 的缓冲区包含未知的内容类型 但同一个文件在 chro
  • 在 JavaScript 中引用 C# 变量

    我已经阅读了很多线程 但我不明白为什么这不起作用 我正在创建一个将用作导航栏的 SharePoint Web 部件 一切都很顺利 直到我尝试在 JS 代码中引用 C 变量 这是来自 VisualWebPart1UserControl asc
  • 未捕获的安全错误:阻止了具有原点的框架...访问具有原点的框架

    我已经为 SAP 解决方案 无论什么 制作了一个组件 该组件通过 iframe 嵌入到报告中 在 SAP 平台 BO 上部署报告后 我收到此错误 在 Chrome 上 但在 IE 或 FF 上也不起作用 Uncaught SecurityE
  • 使用点符号将数字传递到函数中

    如果我有一个对象和函数 var obj 1234 example sample 5678 example sample function example num str if obj num hasOwnProperty str manip
  • 无法安装js-bson

    我正在使用Windows 7 64位 尝试安装bson作为mongodb的依赖项 我收到此错误 npm WARN package json email protected cdn cgi l email protection No READ
  • JavaScript:嵌套循环?

    我想实现这样的动画 序列 动画以循环开始 想象一下car从 x1 移动到 x2 然后暂停 1 秒 然后再次播放动画 想象一下car从 x2 移动到 x3 等 the car循环是通过向汽车左侧添加 1px 来实现的 值 但我无法弄清楚嵌套循
  • jQuery clone() 复制数据...有时...?

    使用下面的示例 我有一个tr我正在复制 它包含一个 jQueryautocomplete 第一次克隆时 自动完成功能不起作用 因为附加的data items 一片空白 第二次单击 添加 按钮时 自动完成功能将起作用 此后 再次单击 添加 会
  • JQuery 屏蔽输入插件不起作用

    我已将 JQuery Masked 输入插件添加到我的 Web 项目中 但它根本不起作用 该插件可以在这里找到 http digitalbush com projects masked input plugin http digitalbu
  • 在状态中检测到不可序列化的值,路径为:`filters.startDate` (redux-toolkit.esm.js )

    我正在使用 React 18 和 Redux 构建一个预算应用程序 我不知道这里有什么问题 import moment from moment const filtersDefaultState text sortBy date start
  • 如何缩放到高图中的特定点

    Highmaps highcharts 是一个 javascript jquery 适配器 可在浏览器等中呈现地图 我有一张突出显示单个国家 地区的地图 但是 世界 地图的比例如此之大 因此我想在将地图加载到相关国家 地区后进行放大 看看
  • Escape String - 在 Javascript 中输出rails字符串[重复]

    这个问题在这里已经有答案了 我正在尝试将字符串值分配给 erb 文件中的 javascript 对象 如下所示 var data name 问题是 如果name is Tom s small ears 的输出data name将会Tom x
  • html 下钻下拉所选值未插入 MYSQL

    我有两个下拉列表 首先从数据库下拉填充 根据第一个下拉列表的选定值从数据库填充第二个下拉列表 document ready function c change function var c1 c selected text if c1 aj
  • 在javascript中我们如何识别一个对象是Hash还是Array?

    我的 JSON 调用的输出可以是数组或哈希 我如何区分这两者 现代浏览器支持Array isArray obj method See MDN https developer mozilla org en US docs Web JavaSc
  • 有没有用 Javascript 编写的开源 JSDoc 解析器?

    我正在寻找一个可以在我的项目中使用的 JSDoc 解析器 我正在寻找可以传递 JSDoc 注释并接收该注释含义的结构化描述的东西 我见过的大多数工具似乎都能够将 JSDoc 注释转换为 HTML 或其他格式 我正在寻找能够提供可用于输入其他

随机推荐

  • 华为OD机试 -身高排序(Java)

    题目描述 小明今年升学到了小学一年级 来到新班级后 发现其他小朋友身高参差不齐 然后就想基于各小朋友和自己的身高差 对他们进行排序 请帮他实现排序 输入描述 第一行为正整数H和N 0 lt H lt 200 为小明的身高 0 lt N lt
  • 大脚战场插件怎么关闭_魔兽战场插件 capping插件怎么关闭

    魔兽世界怎么取消战场插件 字符选择屏幕的左下角有一个插件选项 单击下面的 战场 并单击以禁用它们 它不在游戏中 死亡模式你是说死后的黑白 如果是 点击游戏界面下方的系统选项 进入视频设置 关闭显示设置中的死亡效果 WOW战场capping插
  • Docker Postgres 安装部署指南1.0

    以下为实验版本 Docker version 18 09 2 Postgres 11 4 内容目录 1 确定需要安装的版本 2 获取指定版本镜像 3 指定数据挂载目录 4 启动Postgres服务 5 创建数据库 用户 5 1 进入容器内部
  • 【前后端】将代码上传到gitee

    文章目录 前台 gitee建立仓库 步骤A 如果是双人 则有步骤B 后台 gitee建立仓库 复制链接 代码拷贝 提交 小记录一波 前台 gitee建立仓库 步骤A 初始化 commit 后面单引号随便写 git init git add
  • VLAN划分及配置注意事项

    VLAN Virtual Local Area Network 即虚拟局域网 是将一个物理的LAN在逻辑上划分成多个广播域的通信技术 VLAN内的主机间可以直接通信 而VLAN间不能直接通信 从而将广播报文限制在一个VLAN内 VLAN之间
  • Spark Streaming VS Flink

    架构对比 运行角色 Spark Streaming 运行时的角色 standalone 模式 主要有 Master 主要负责整体集群资源的管理和应用程序调度 Worker 负责单个节点的资源管理 driver 和 executor 的启动等
  • MT6701磁编码器使用指南,14Bit单圈绝对值,I2C stm32 HAL库读角度,兼容AS5600

    MT6701是麦歌恩 MagnTek 公司的磁性角度传感器芯片 提供14Bit 0 360 单圈绝对角度检测 拥有 ABZ PWM 模拟量 I2C SSI 等多种信息输出方式 还可根据磁场强度的瞬时变化提供非接触式按压检测功能 能够以较低的
  • ENVI 5.3 分类后类别合并

    想把粉色的云层合并到林地 选择 Combine Classes 输出为 白云类别与林地合并
  • iOS支付功能

    文章转载自 https www jianshu com p 8eb14edca8fb 1 简介 iOS支付主要分两类 第三方支付和应用内支付 内购 其中第三方支付包括有 支付宝支付 微信支付 银联支付 百度钱包支付 京东支付等 应用内支付
  • 高性能javascript--算法和流程控制

    for while和do while性能相当 避免使用for in循环 除非遍历一个属性量未知的对象 es5 for in 遍历的对象便不局限于数组 还可以遍历对象 原因 for in每次迭代操作会同时搜索实例或者原型属性 for in 循
  • Linux系统:centos7.2忘记密码怎么办?

    在使用centos系统的时候有时候太久没用忘记登录密码了 这时候该怎么办呢 下面就来教教大家怎么重置root管理员的密码 1 启动系统 在GRUB2引导画面 按E键 编辑引导项 2 删除linux16这一行最后的 rhgb和 quiet参数
  • vue3中如何以函数的形式创建组件并挂载

    在日常开发中 我们可能会遇到一种情况 组件太灵活 且无法预先定义 那么我们就需要能够创建组件的能力 并且能组合到我们现有的界面中 基础API javascript 创建 app component name 组合
  • Uniapp零基础开发学习笔记(4) -顶部导航栏titleNView的制作

    Uniapp零基础开发学习笔记 4 顶部导航栏titleNView的制作 制作顶部导航栏titleNView的过程 1 官网上关于顶部导航栏的介绍 https uniapp dcloud net cn collocation pages h
  • sqlserver转换时间为字符串

    convert varchar 8 getdate 112 把
  • 文件预览:使用xlsx预览excel文件

    文件预览系列 mavon editor预览Markdown文件 xlsx预览excel文件 注意事项 多sheet页的情况需要自己手动处理 一 安装插件 xlsx 我目前使用的是0 17 5版本 之前有一次升级后报错 如果xlsx内部报错
  • C++deque容器deque 排序

    C deque容器deque 排序 功能描述 利用算法实现对deque容器进行排序 include
  • 关于BMC ipmi oem cmd和redfish

    ipmi是一个适用于bmc的标准协议 开发者可以通过ipmi oem cmd和bmc交互 oem cmd的实现与组成 均为unsigned char类型 NetFunction Cmd Request data Response data
  • Avue 远程搜索输入框,联动赋值其他组件 v2.7.10及以下

    Avue版本 v2 7 10及以下适用本文 v2 8 0及以上版本点这里 文章目录 Avue 远程搜索输入框 联动赋值 前言 一 基于avue自带属性实现 效果差 二 基于element ui实现 效果好 总结 Avue 远程搜索输入框 联
  • 用户登录非常慢报如下错误/usr/bin/xauth: timeout in locking authority file /home/user/.Xauthority

    一般是因为在创建用户时 用户家目录属组和属主不对导致 以最简单的解决办法就是给用户家目录赋予用户的所有权限就能解决 再次登录时 会重新创建一个这样的文件 下次登录就快了 chow user user user
  • 【JavaScript】Function的祖传方法call与apply

    引言 内容速递 看了本文您能了解到的知识 在本篇文章中 将带你了解什么是call和apply call和apply的用途 如何手写call和apply以及call和apply的使用场景 1 什么是call和apply call 和apply