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

news2024/11/26 12:26:30

在这里插入图片描述

引言

内容速递

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

在本篇文章中,将带你了解什么是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,公众号【归子莫】,小程序【子莫说】

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

幸好我在,感谢你来!

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

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

相关文章

面试题:redis是单线程、StringBuffer是线程安全的

1、说明String 和StringBuffer的区别 类底层/ 可变&#xff1f;线程安全Stringfinal char[] 不可变是StringBuffer char[] 可变 是&#xff08;synchronized方法&#xff09;StringBuilder char[] 可变否 (4条消息) Java基础&#xff1a;String、StringBuffer、…

mcu 启动流程

MCU启动流程 MCU启动流程 MCU启动流程1 MCU的启动方式2 MCU程序启动执行过程3 启动过程的执行工作4 keil调式过程验证 1 MCU的启动方式 单片机的启动方式&#xff0c;以stm32为例&#xff0c;如下&#xff1a; 不同的下载方式对应的不同的启动方式&#xff0c;stm32主要有三种…

【最新教程】树莓派安装系统及VNC远程桌面连接

大家好&#xff0c;今天就不给大家介绍PYTHONL ,今天我作为一个刚入坑树莓派的小白&#xff0c;整理了一下自己安装树莓派的整个过程&#xff0c;分享给大家。 目录 树莓派 准备工作&#xff1a; 树莓派远程ssh失败access denied 原因&#xff1a; 树莓派系统安装 1、下载…

ImportError: cannot import name ‘imresize‘ from ‘scipy.misc‘

ImportError: cannot import name ‘imresize’ from ‘scipy.misc’ 今天在运行项目时发现了部分代码出现问题&#xff0c;报错图片如下&#xff1a; 通过了解得知&#xff0c;imresize已经被最新版本的Scipy库弃用了&#xff0c;所以在这里&#xff0c;处理这种错误可以选择…

ylb-接口6验证手机号是否注册

总览&#xff1a; 1、service处理 在api模块下service包&#xff0c;创建一个UserService接口&#xff1a;&#xff08;根据手机号查询数据queryByPhone(String phone)&#xff09; package com.bjpowernode.api.service;import com.bjpowernode.api.model.User; import co…

前端 | (六)CSS盒子模型 | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 文章目录 &#x1f4da;元素的显示模式&#x1f407;CSS长度单位&#x1f407;元素的显示模式⭐️块元素&#xff08;block&#xff09;⭐️行内元素&#xff08;inline&#xff09…

网工内推 | 美图秀秀招网工,大专以上,15薪,NP认证优先

01 美图公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、美图大厦网络、分公司网络、IT相关项目的网络、办公内网服务器&#xff1b; 2、负责网络的设计、运行、管理和维护等工作&#xff1b; 3、负责远程办公环境的优化、运行、管理和维护工作&#xff1b; 4、…

UnxUtils工具包,Windows下使用Linux命令

1. 前言 最近写批处理多了&#xff0c;发现Windows下的bat批处理命令&#xff0c;相比Linux的命令&#xff0c;无论是功能还是多样性&#xff0c;真的差太多了。但有时候又不得不使用bat批处理&#xff0c;好在今天发现了一个不错的工具包&#xff1a;UnxUtils&#xff0c;这个…

Generative Adversarial Network

Goodfellow,2014年 文献阅读笔记--GAN--Generative Adversarial NetworkGAN的原始论文-组会讲解_gan英文论文_Flying Warrior的博客-CSDN博客 启发:如何看两个数据是否来自同一个分布? 在统计中,two sample test。训练一个二分类的分类器,如果能分开这两个数据,说明来自…

基于MSP432P401R跟随小车【2022年电赛C题】

文章目录 一、赛前准备1. 硬件清单2. 工程环境 二、赛题思考三、软件设计1. 路程、时间、速度计算2. 距离测量3. 双机通信4. 红外循迹 四、技术交流 一、赛前准备 1. 硬件清单 主控板&#xff1a; MSP432P401R测距模块&#xff1a; GY56数据显示&#xff1a; OLED电机&#x…

实现本地缓存-caffeine

目录 实现caffeine cache CacheManager Caffeine配置说明 创建自定义配置类 配置缓存管理器 编写自动提示配置文件 测试使用 创建测试配置实体类 创建测试配置类 创建注解扫描的测试实体 创建单元测试类进行测试 实现caffeine cache CacheManager SimpleCacheManag…

HttpClient使用MultipartEntityBuilder上传文件时乱码问题解决

HttpClient使用MultipartEntityBuilder是常用的上传文件的组件&#xff0c;但是上传的文件名称是乱码&#xff0c;一直输出一堆的问号&#xff1a; 如何解决呢&#xff1f;废话少说&#xff0c;先直接上代码&#xff1a; public static String doPostWithFiles(HttpClient http…

git使用问题记录-权限

注意点&#xff1a; 1、在远程仓库中直接创建项目时&#xff0c;默认分支为main 2、git push报错 原因&#xff1a;即使是项目文件的创建者&#xff0c;但上层目录的权限为developer&#xff0c;无法push项目&#xff0c;找上层管理员修改权限为maintainer或owner可push代码…

File格式转换MultipartFile格式的例子

首先&#xff1a;需要先引入依赖包 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.9</version> </dependency> 1.Multipartfile转File类型 //创建一…

函数(function)py

具有名称的&#xff0c;是为了解决某一问题&#xff0c;功能代码的集合 目录 一.定义函数 二.函数的分类 三.局部变量和全局变量 1.局部变量(local variable) 2.全局变量(global variable) 四.函数调用的内存分析 五.函数的参数 1.默认值参数 2.可变参数(不定长参数)…

基于timegan扩增技术,进行多维度数据扩增(Python编程,数据集为瓦斯浓度气体数据集)

1.数据集介绍 瓦斯是被预测气体&#xff0c;其它列为特征列,原始数据一共有472行数据&#xff0c;因为原始数据比较少&#xff0c;所以要对原始数据&#xff08;总共8列数据&#xff09;进行扩增。 开始数据截图 截止数据截图 2. 文件夹介绍 lstm.py是对未扩增的数据进行训练…

1186. 删除一次得到子数组最大和;1711. 大餐计数;1834. 单线程 CPU

1186. 删除一次得到子数组最大和 解题思路&#xff1a;如果没做过还不是很好想&#xff0c;当时自己第一反应是双指针&#xff0c;结果是个动态规划的题。 核心就是dp的定义&#xff0c;dp[i][k]表示以arr[i]结尾删除k次的最大和。看到这里其实就有一点思路了 dp[i][0]表示以…

KGAT: Knowledge Graph Attention Network for Recommendation

[1905.07854] KGAT: Knowledge Graph Attention Network for Recommendation (arxiv.org) LunaBlack/KGAT-pytorch (github.com) 目录 1、背景 2、任务定义 3、模型 3.1 Embedding layer 3.2 Attentive Embedding Propagation Layers 3.3 Model Prediction 3.4 Optimi…

docker容器引擎(一)

docker 一、docker的理论部分docker的概述容器受欢迎的原因容器与虚拟机的区别docker核心概念 二、安装docker三、docker镜像操作四、docker容器操作 一、docker的理论部分 docker的概述 一个开源的应用容器引擎&#xff0c;基于go语言开发并遵循了apache2.0协议开源再Linux容…

ThreeJS打造自己的人物

hello&#xff0c;大家好&#xff0c;我是better&#xff0c;今天为大家分享如何使用Three打造属于自己的3D人物模型。 人物建模 当下有很多人物建模的网站&#xff0c;这里给大家分享的 Ready Player Me - Create a Full-Body 3D Avatar From a Photo 前往这个网址&#xff…