说一下this,实现apply、call

news2024/12/28 20:38:29

理解this

在ES5中,this的指向始终坚持一个原理:“this永远指向最后调用它的那个对象”,切记这句话。下面看几个例子。

例一

var obj = {name: 'zhangsan',say: function() {console.log(this.name);}
}

obj.say() // zhangsan 

最基本的使用,函数say是obj调用的,函数中的this指向obj,所以打印的是zhangsan

例二

var name = 'lisi'
var obj = {name: 'zhangsan',say: function() {console.log(this.name);}
}

var func = obj.say
func() // lisi 

把say赋给变量func,然后调用func,前面没有调用对象,那么此时的调用对象就是全局对象window,因此this指向window,所以打印lisi

例三

var name = 'lisi'
var obj = {name: 'zhangsan',say: function() {console.log(this.name);}
}

window.obj.say() // zhangsan 

this永远指向最后调用它的那个对象,这里虽然前面加了个window,但是最后调用者还是obj,因此this指向obj。

可以看出:this的指向并不是在创建的时候就可以确定的。在ES5中,this永远指向最后调用它的那个对象

例四

var name = 'lisi'

var obj = {name: 'zhangsan',say: function() {console.log(this.name); // zhangsanfunc()function func() {console.log(this.name); // lisi}},

}

obj.say() 

say的调用者是obj,say函数内部的this就指向obj,因此第一次打印zhangsan。再调用func,此时func的调用者是window,func函数内部的this就指向window,因此第二次打印lisi

如何改变this指向

改变this指向有以下几种方法:

  • 使用ES6的箭头函数
  • 把this存一下然后调用:_this=this
  • 使用apply、call、bind改变this指向

箭头函数

ES6中的箭头函数可以避免ES5中很多this的坑。箭头函数中的this始终指向函数定义时的this,而非执行时。箭头函数中没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最近一次非箭头函数的this

例五

var name = 'lisi'

var obj = {name: 'zhangsan',func1: function() {console.log(this.name);},func2: function() {setTimeout(function() {this.func1()}, 1000)}
}

obj.func2() // this.func1 is not a function 

如上代码,调用func2报错,因为最终调用setTimeout的对象是window,函数里的this指向window,而window中并没有func1,所以报错。

这里补充个知识点:匿名函数的this永远指向window。this永远指向最后调用它的那个对象。我们来找找最后调用匿名函数的对象是哪个,很尴尬,匿名函数没有名字,所以是没有办法被其他对象调用的,所以只能在window下执行。因此匿名函数的this始终指向window。

var name = 'lisi'

var obj = {name: 'zhangsan',func1: function() {console.log(this.name);},func2: function() {// 使用箭头函数setTimeout(() => {this.func1()}, 1000)}
}

obj.func2() // zhangsan 

使用箭头函数即可解决问题,此时this就是func2函数中的this,而func2是obj调用的,因此this实际指向obj。

_this=this

简单把this存一下即可

例六

var name = 'lisi'

var obj = {name: 'zhangsan',func1: function() {console.log(this.name);},func2: function() {// this存一下在匿名函数里使用var _this = thissetTimeout(function() {_this.func1()}, 1000)}
}

obj.func2() // zhangsan 

apply、call

apply和call都是用来改变this指向的,作用相同,用法稍微有点不同。

MDN中apply定义如下:

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

用法:

func.apply(thisArg, ArrayArg) 

call定义如下:

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

用法:

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

详细用法见MDN。

例七

var name = 'lisi'

var obj = {name: 'zhangsan',say: function(arg) {console.log(this.name, arg);}
}

var func1 = obj.say // lisi 1
func1(1) 

此时this指向window,打印lisi 1。如果我们现在仍想要this指向obj,那么就可通过apply改变this:

var name = 'lisi'

var obj = {name: 'zhangsan',say: function(arg) {console.log(this.name, arg);}
}

var func1 = obj.say // zhangsan 2
func1.apply(obj, [2]) 

通过apply改变this指向,指向obj。然后通过一个数组传入调用时的参数。这就是apply用法的简单实例。call用法与其类似,只不过不是通过数组传参,而是需要显式传入多个参数。

var name = 'lisi'

var obj = {name: 'zhangsan',say: function(arg) {console.log(this.name, arg);}
}

var func1 = obj.say // zhangsan 3
func1.call(obj, 3) 

手动实现apply、call

理解了this、this指向、apply、call后我们可以思考一下如何手动实现apply、call。

手动实现apply

Function.prototype._apply = function(context) {// context是要绑定的this// this是调用的函数即func1if (typeof this !== "function") {throw new Error('只能在函数上调用')return}context = context || windowlet result = null// 把需要执行的函数赋给context,由context调用即可context.fn = thisif (arguments[1]) {result = context.fn(...arguments[1])} else {result = context.fn()}delete context.fnreturn result
}

var name = 'lisi'

var obj = {name: 'zhangsan',say: function(arg) {console.log(this.name, arg);}
}

var func1 = obj.say // zhangsan 1
func1._apply(obj, [1]) 

总结步骤如下:

  • 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。* 判断传入上下文对象是否存在,如果不存在,则设置为 window 。* 将函数作为上下文对象的一个属性。* 判断参数值是否传入* 使用上下文对象来调用这个方法,并保存返回结果。* 删除刚才新增的属性* 返回结果手动实现call

Function.prototype._call = function(context) {// context是要绑定的this// this是调用的函数即func1if (typeof this !== "function") {throw new Error('只能在函数上调用')return}context = context || windowconst arg = Array.from(arguments).splice(1)let result = null// 把需要执行的函数赋给context,由context调用即可context.fn = thisresult = context.fn(...arg)delete context.fnreturn result
}

var name = 'lisi'

var obj = {name: 'zhangsan',say: function(arg) {console.log(this.name, arg);}
}

var func1 = obj.say // zhangsan 2
func1._call(obj, 2) 

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

突破年薪百万难关!吃透这套Java真题合集

前言我相信大多 Java 开发的程序员或多或少经历过BAT一些大厂的面试,也清楚一线互联网大厂 Java 面试是有一定难度的,小编经历过多次面试,有满意的也有备受打击的。因此呢小编想把自己这么多次面试经历以及近期的面试真题来个汇总分析&#x…

【软考——系统架构师】架构、系分、软设的区别和联系

🔎这里是【软考——系统架构师】,关注我考试轻松过线 👍如果对你有帮助,给博主一个免费的点赞以示鼓励 欢迎各位🔎点赞👍评论收藏⭐️ 文章目录👀三科相同点👀三科不同点--上午题&am…

【Classical Network】EfficientNetV2

原文地址 原文代码 pytorch实现1 pytorch实现2 详细讲解 文章目录EfficientNet中存在的问题NAS 搜索EfficientNetV2 网络结构codeEfficientNet中存在的问题 训练图像尺寸大时,训练速度非常慢。train size 512, batch 24时,V100 out of memory在网络浅…

联想笔记本无法下载 Lenovo Vantage

状况 在 Microsoft Store 下载时发生错误,可能是如下代码:0x80070005, 0x80073D05, or 0x80070017. 解决方法 1.在“开始”菜单搜索栏中输入PowerShell 2.当Windows PowerShell出现在“开始”菜单中,右键点击此图标,然后选择以…

AWS攻略——使用中转网关(Transit Gateway)连接同区域(Region)VPC

文章目录环境准备创建VPC配置中转网关给每个VPC创建Transit Gateway专属挂载子网创建中转网关创建中转网关挂载修改VPC的路由验证创建业务Private子网创建可被外网访问的环境测试子网连通性Public子网到Private子网Private子网到Private子网知识点参考资料在《AWS攻略——Peeri…

I.MX6ULL_Linux_系统篇(18) uboot移植

原厂uboot 编译 uboot 的移植并不是说我们完完全全的从零开始将 uboot 移植到我们现在所使用的开发板或者开发平台上。这个对于我们来说基本是不可能的,这个工作一般是半导体厂商做的, 半导体厂商负责将 uboot 移植到他们的芯片上,因此半导体…

网易云音乐财报解读:收入大增亏损收窄,“云村”草长莺飞

独家版权时代结束后,在线音乐产业进入了新的发展阶段,各家音乐平台经营状况备受关注。 2月23日,网易云音乐公布了2022年全年财务业绩。财报显示,网易云音乐2022年全年收入为90亿元,较2021年同比增长28.5%。 值得一提的…

IDEA集成Git

1:IDEA集合Git1.1:配置Git忽略文件-IDEA特定文件问题 1:为什么要忽略他们?答: 与项目的实际功能无关, 不参与服务器上部署运行。把它们忽略掉能够屏蔽 IDE 工具之间的差异。问题 2:怎么忽略?1&a…

分布式锁zookeeper实现详解原理及落地方案

吐血推荐:最近整理之前面试BAT的材料,写了一份《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。 领取方法: Java面试BATJ通关手册 介绍 当一个应用程序需要在分布式系统中对共…

cmake demo

工程描述 1,为工程添加一个子目录src,用来存储源代码; 2,添加一个子目录doc,用来存储这个工程的文档hello.txt 3,在工程目录添加文本文件COPYRIGHT, README; 4,在工程目录添加一个runhello.sh …

18_FreeRTOS任务通知

目录 任务通知的简介 任务通知值的更新方式 任务通知的优势 任务通知的劣势 任务通知值和通知状态 发送通知相关API函数 接收通知相关API函数 任务通知模拟信号量实验 任务通知模拟消息邮箱实验 任务通知模拟事件标志组实验 任务通知的简介 任务通知:用来通知任务的…

JVM 学习(1)—JVM 与 JMM 内存模型简单理解

一、JVM 内存模型概述 (1) 为什么会出现 JVM 内存模型呢? JVM 内存模型是为规范描述 Java 虚拟机在执行 Java 程序时,将程序中的数据和代码存储到计算机内存中的方式和规则。JVM 内存模型定义 Java 虚拟机所使用的内存结构以及内存区域之间的关系&…

数据归档,存储的完美储备军

数据爆炸性增长的同时,存储成为了大家首要担心的问题大家都希望自家数据保存20年、50年后仍完好无损但是,N年后的数据量已达到一个无法预测的峰值如此大量的数据在保存时极可能存在丢失、损坏等问题这时需要提前对数据进行“备份”、“归档”备份是对数据…

Linux->进程概念于基本创建

1. 进程基本概念 当一个可执行程序被加载到内存当中,并由操作系统将其管理起来,此时这个程序就被称之为进程。也就是下方的: 程序的一个执行实例,正在执行的程序等 担当分配系统资源(CPU时间,内存&#xff…

阿里云云通信风控系统的架构与实践

作者:铭杰 阿里云云通信创立于 2017 年,历经 5 年发展已经孵化出智能消息、智能语音、隐私号、号码百科等多个热门产品。目前,已成为了国内云通信市场的领头羊,在国际市场上服务范围也覆盖了 200 多个国家。随着业务的不断壮大&am…

正则表达式是如何运作的?

在日常的开发工作当中&#xff0c;我们必不可免的会碰到需要使用正则的情况。 正则在很多时候通过不同的组合方式最后都可以达到既定的目标结果。比如我们有一个需要匹配的字符串&#xff1a; hello&#xff0c;我们可以通过 / .</p>/ 以及 / .?</p>/ 来匹配&…

数据分析与SAS学习笔记8

过程步&#xff1a;一个典型的SAS完整程序&#xff1a; 代码说明&#xff1a; 1&#xff09;reg&#xff1a;回归分析&#xff1b; 2&#xff09;model&#xff1a;因变量和自变量。 proc开头部分叫过程步。 常用过程&#xff1a; SORT过程&#xff1a; PRINT过程与FORTMAT…

Linux环境下绕过长度限制写入webshell

前提当命令执行漏洞对长度有限制时&#xff0c;我们可以通过一些Linux命令生成文件进行执行。如果应用程序是使用shell解析器来执行命令或文件&#xff0c;那么不需要该文件具有执行属性(x)如果应用程序没有shell解析器&#xff0c;那么写入文件内容时&#xff0c;可以第一条语…

终极方案,清理 docker 占用磁盘过大问题, 亲测有效!

背景 在笔者的工作测试环境中&#xff0c;使用过程中突然出现根磁盘快吃满了&#xff08;docker也是使用的根池盘的/var/lib/docker&#xff09;&#xff0c; wtf &#xff1f; 服务用不了&#xff1f; 当然网上找到了一些常规的清楚docker 日志文件 但是通过df -hT 查看到over…

2023年Java面试题精选(蚂蚁金服/滴滴/美团/拼多多腾讯)

作为一名优秀的程序员&#xff0c;技术面试都是不可避免的一个环节&#xff0c;一般技术面试官都会通过自己的方式去考察程序员的技术功底与基础理论知识。 如果你参加过一些大厂面试&#xff0c;肯定会遇到一些这样的问题&#xff1a; 1、看你项目都用的框架&#xff0c;熟悉…