this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)

this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)一 学习 this 的指向问题二 call 和 apply 的特点与区别三 模拟实现一个 call 四 bind 的功能五 结尾思考题 导图 示例代码 让你轻松掌握

this指向详解

JavaScript基础提升合集

?包含this、call、原型链、作用域等基础经典知识点
☕️每周一篇,打好基础,爬升不累
?最全知识点解析,易懂的代码示例收藏方便阅读
?完整版在线阅读,猛戳这里~










目录

前言+思考题

记得当时找实习的时候,总是会在简历上加上一句——熟悉Js,例如this指向、call、apply等…

this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)

而每次投递简历时我都会经历如下步骤

  • 面试前,去问度娘——this指向可以分为哪几种啊~、call和apply的区别是什么?底气由0% 猛涨到了 50%;
  • 面试中,面试官随便扔上来几道题,我都可以“坚定的”给出答案,结果总是不尽人意…
  • 面试后,我会羞愧的删除掉简历上的这一条。而再之后投递简历时我又再次加上了这一条…

this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)

思考题

下面几道题是我在网上搜索出来的热度较高的问题,如果大佬们可以轻松的回答上,并有清晰的思路,不妨直接点个赞吧(毕竟也消耗了不少脑细胞),如果大佬们能在评论处指点一二,就更好了!!!

填空题:

  • 执行Javascript中的【 】函数会创建一个新函数,新函数与被调函数具有相同的函数体,当目标函数被调用时 this 值指向第一个参数。

问答题:

  • 请你谈一下改变函数内部this指针的指向函数有哪几种,他们的区别是什么?
  • this的指向可以分为哪几种?

代码分析题:

var name = 'window' var person1 = { 
    name: 'person1', show1: function () { 
    console.log(this.name) }, show2: () => console.log(this.name), show3: function () { 
    return function () { 
    console.log(this.name) } }, show4: function () { 
    return () => console.log(this.name) } } var person2 = { 
    name: 'person2' } person1.show1() person1.show1.call(person2) person1.show2() person1.show2.call(person2) person1.show3()() person1.show3().call(person2) person1.show3.call(person2)() person1.show4()() person1.show4().call(person2) person1.show4.call(person2)() 

一、this的指向

百度、谷歌上输入“this的指向”关键字,大几千条文章肯定是有的,总不至于为了全方面、无死角的掌握它就要将所有的文章都看一遍吧?所以不如梳理出一个稳固的框架,顺着我们的思路来填充它。

思维导图

在这里插入图片描述

本节精华:
  • this 总是(非严格模式下)指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境;
  • 除了不常用的with和eval的情况,具体到实际应用中,this指向大概可以分为四种:
    • 作为对象的方法调用;
    • 作为普通函数调用;
    • 构造器调用;
    • call 或 apply调用;
    • 箭头函数中,this指向函数上层作用域的this;
  • 构造器普通函数的区别在于被调用的方式
  • A,call(B) => 可以理解成在B的作用域内调用了A方法;
分析

1、作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象

var obj = { 
    a: 'yuguang', getName: function(){ 
    console.log(this === obj); console.log(this.a); } }; obj.getName(); // true yuguang 

2、作为普通函数调用

当函数不作为对象的属性被调用,而是以普通函数的方式,this总是指向全局对象(在浏览器中,通常是Window对象)

window.name = 'yuguang'; var getName = function(){ 
    console.log(this.name); }; getName(); // yuguang 

或者下面这段迷惑性的代码:

window.name = '老王' var obj = { 
    name: 'yuguang', getName: function(){ 
    console.log(this.name); } }; var getNew = obj.getName; getNew(); // 老王 

而在ES5的严格模式下,this被规定为不会指向全局对象,而是undefined

3、构造器调用

除了一些内置函数,大部分Js中的函数都可以成为构造器,它们与普通函数没什么不同

构造器普通函数的区别在于被调用的方式
当new运算符调用函数时,总是返回一个对象,this通常也指向这个对象

var MyClass = function(){ 
    this.name = 'yuguang'; } var obj = new MyClass(); obj.name; // yuguang 

但是,如果显式的返回了一个object对象,那么此次运算结果最终会返回这个对象。

var MyClass = function () { 
    this.name = 1; return { 
    name: 2 } } var myClass = new MyClass(); console.log('myClass:', myClass); // { name: 2} 

只要构造器不显示的返回任何数据,或者返回非对象类型的数据,就不会造成上述问题。

4、call或apply调用

跟普通的函数调用相比,用call和apply可以动态的改变函数的this

var obj1 = { 
    name: 1, getName: function (num = '') { 
    return this.name + num; } }; var obj2 = { 
    name: 2, }; // 可以理解成在 obj2的作用域下调用了 obj1.getName()函数 console.log(obj1.getName()); // 1 console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4 console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4 

5.箭头函数

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。

因此,在下面的代码中,传递给getVal函数内的this并不是调用者自身,而是外部的this~

this.val = 2; var obj = { 
    val: 1, getVal: () => { 
    console.log(this.val); } } obj.getVal(); // 2 

常见的坑

就像标题一样,有的时候this会指向undefined

情况一

var obj = { 
    name: '1', getName: function (params) { 
    console.log(this.name) } }; obj.getName(); var getName2 = obj.getName; getName2(); 

这个时候,getName2()作为普通函数被调用时,this指向全局对象——window。

情况二

当我们希望自己封装Dom方法,来精简代码时:

var getDomById = function (id) { 
    return document.getElementById(id); }; getDomById('div1') //dom节点 

那么我们看看这么写行不行?

var getDomById = document.getElementById getDomById('div1') // Uncaught TypeError: Illegal invocation(非法调用) 

这是因为:

  • 当我们去调用document对象的方法时,方法内的this指向document
  • 当我们用getId应用document内的方法,再以普通函数的方式调用,函数内容的this就指向了全局对象。

利用call和apply修正情况二

document.getElementById = (function (func) { 
    return function(){ 
    return func.call(document, ...arguments) } })(document.getElementById) // 利用立即执行函数将document保存在作用域中 

this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)

二、call和apply

不要因为它的“强大”而对它产生抗拒,了解并熟悉它是我们必须要做的,共勉!

思维导图

在这里插入图片描述

1.call和apply区别

先来看区别,是因为它们几乎没有区别,下文代码实例call和apply都可以轻易的切换。

当它们被设计出来时要做到的事情一摸一样,唯一的区别就在于传参的格式不一样

  • apply接受两个参数
    • 第一个参数指定了函数体内this对象的指向
    • 第二个参数为一个带下标的参数集合(可以是数组或者类数组)
  • call接受的参数不固定
    • 第一个参数指定了函数体内this对象的指向
    • 第二个参数及以后为函数调用的参数

因为在所有(非箭头)函数中都可以通过arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,它本身就是一个类数组,我们apply在实际使用中更常见一些。

call是包装在apply上面的语法糖,如果我们明确的知道参数数量,并且希望展示它们,可以使用call。

当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会默认指向宿主对象,在浏览器中则是window

借用其他对象的方法

我们可以直接传null来代替任意对象

Math.max.apply(null, [1, 2, 3, 4, 5]) 
2.call和apply能做什么?

使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数——来时MDN

  • 调用构造函数来实现继承;
  • 调用函数并且指定上下文的 this;
  • 调用函数并且不指定第一个参数;

1.调用构造函数来实现继承

通过“借用”的方式来达到继承的效果:

function Product(name, price) { 
    this.name = name; this.price = price; } function Food(name, price) { 
    Product.call(this, name, price); // this.category = food; } var hotDog = new Food('hotDog', 20); 

2.调用函数并且指定上下文的 this

此时this被指向了obj

function showName() { 
    console.log(this.id + ':' + this.name); }; var obj = { 
    id: 1, name: 'yuguang' }; showName.call(obj) 

3.使用call单纯的调用某个函数

Math.max.apply(null, [1,2,3,10,4,5]); // 10 

this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)

三、模拟实现一个call

先来看一下call帮我们需要做什么?

var foo = { 
    value: 1 }; function show() { 
    console.log(this.value); }; show.call(foo); //1 

就像解方程,要在已知条件中寻找突破哦口:

  • call 使得this的指向变了,指向了foo;
  • show 函数被执行了;
  • 传入的参数应为 this + 参数列表;

第一版代码

上面提到的3点,仅仅完成了一点,且传入的参数

var foo = { 
    value: 1 }; function show() { 
    console.log(this.value); }; Function.prototype.setCall = function (obj) { 
    console.log(this); // 此时this指向show obj.func = this; // 将函数变成对象的内部属性 obj.func(obj.value); // 指定函数 delete obj.func // 删除函数,当做什么都没发生~ } show.setCall(foo); 

第二版代码

为了解决参数的问题,我们要能获取到参数,并且正确的传入:

var foo = { 
    value: 1 }; function show(a, b) { 
    console.log(this.value); console.log(a + b); }; Function.prototype.setCall = function (obj) { 
    obj.fn = this; // 将函数变成对象的内部属性 var args = []; for(let i = 1; i < arguments.length; i++){ 
    args.push('arguments[' + i + ']'); } eval('obj.fn(' + args + ')'); // 传入参数 delete obj.fn; // 删除函数,当做什么都没发生~ } show.setCall(foo, 1, 2); // 1 3 

此时,我们就可以做到,传入多个参数的情况下使用call了,但是如果你仅想用某个方法呢?

第三版代码

Function.prototype.setCall = function (obj) { 
    var obj = obj || window; obj.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { 
    args.push('arguments[' + i + ']'); } var result = eval('obj.fn(' + args +')'); delete obj.fn; return result; }; // 测试一下 var value = 2; var obj = { 
    value: 1 }; function bar(name, age) { 
    console.log(this.value); return { 
    value: this.value, name: name, age: age } } bar.setCall(null); // 2 console.log(bar.setCall(obj, 'yuguang', 18)); 

四、bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用 —— MDN

提到了callapply,就绕不开bind。我们试着来模拟一个bind方法,以便加深我们的认识:

Function.prototype.bind = function (obj) { 
    var _this = this; // 保存调用bind的函数 var obj = obj || window; // 确定被指向的this,如果obj为空,执行作用域的this就需要顶上喽 return function(){ 
    return _this.apply(obj, arguments); // 修正this的指向 } }; var obj = { 
    name: 1, getName: function(){ 
    console.log(this.name) } }; var func = function(){ 
    console.log(this.name); }.bind(obj); func(); // 1 

这样看上去,返回一个原函数的拷贝,并拥有指定的 this 值,还是挺靠谱的哦~

写在最后

JavaScript内功基础部分第一篇,总结这个系列是受到了冴羽大大的鼓励和启发,本系列大约会有15篇文章,都是我们在面试最高频的,但在工作中常常被忽略的。

热门开源-欢迎star支持

  • 前端进阶
  • 高频经典手撕代码实现
  • 剑指Offer题解
  • LeetCode题解

关于我

  • 花名:余光
  • Writing Vue and JavaScript
  • Working at GaoDing Design
  • A console log tester

this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/198144.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午3:42
下一篇 2026年3月26日 下午3:42


相关推荐

  • python 列表转字符串[通俗易懂]

    python 列表转字符串[通俗易懂]python中list转字符串命令:”.join(list)其中,引号中是字符之间的分割符,如“,”,“;”,“\t”等等如:list=[1,2,3,4,5]”.join(list)结果即为:12345′,’.join(list)结果即为:1,2,3,4,5

    2022年6月13日
    29
  • 【uboot】imx6ull uboot移植LAN8720A网卡驱动

    【uboot】imx6ull uboot移植LAN8720A网卡驱动文章目录相关文章1.前言2.IMX6ULLEthernetLAN8720A硬件连接3.支持LAN8720A修改步骤4.验证测试问题1:如何确定LAN8720A网卡PHYAD地址?问题2:如何确定devicetree中对resetgpio的定义?问题3:LAN8720A网卡nINTSEL是如何配置?问题4:IMX6ULLETH是如何被初始化的?相关文章1.《【uboot】imx6ulluboot2020.04源码下载和编译环境配置》2.《【Ethernet】以太网卡LAN8720

    2022年6月29日
    50
  • java IO流详解

    java IO流详解

    2021年6月13日
    121
  • 小红书图文生成实战[可运行源码]

    小红书图文生成实战[可运行源码]

    2026年3月17日
    5
  • 在Android手机上对https请求进行抓包

    在Android手机上对https请求进行抓包前段时间跟QQ群里的群友聊天时无意聊到了抓包的话题。抓包可以说是程序员日常开发调试问题的一个重要手段,可以帮助我们理清客户端与服务器之间的数据传输问题,以便于甩锅。在过去,网络请求基本都是靠的http协议,那个时候的抓包是一件非常简单的事情。然而这几年,http协议在逐渐被淘汰,几乎所有的网络请求都变成了https协议,这就使事情变得复杂了。群里一位朋友说,https是不可能被抓包的,不然怎么保证https传输的安全性,毕竟那么多大公司都在用这个协议来传输重要的数据。这其实是一个比较有意思的话题

    2022年6月25日
    61
  • Burp Collaborator 使用总结

    Burp Collaborator 使用总结0x00 使用原因我们在做渗透测试的时候 经常会遇到这种情况 测试跨站可能有些功能插入恶意脚本后无法立即触发 例如提交反馈表单 需要等管理员打开查看提交信息时才会触发 或者是盲注跨站 盲打 XSS 这种 再例如 SSRF 如果程序不进行回显任何信息 而只提示你输入的是否合法 那么也无法直接判断程序存在 SSRF 漏洞 我们可以叫盲 SSRF 再例如 XXE 引入外部文件时 如果程序也不返回任何

    2025年10月20日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号