无人不识又无人不迷糊的this

关于this

this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

为什么要用this

随着开发者的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用this则不会这样。

比如下面的例子:

function identify() {
  return this.name.toUpperCase();
}

function speak() {
  var greeting = "Hello, I'm " + identify.call(this);
  console.log(greeting);
}

var me = {
  name: 'Kyle',
};

var you = {
  name: 'Reader',
};

console.log(identify.call(me)); 
console.log(identify.call(you)); 

speak.call(me); 
speak.call(you);

打印一下结果:

2-1.png

上面的代码可以在不同的上下文对象(me和you)中重复使用函数identify()和speak(),不用针对每个对象编写不同版本的函数。如果不使用this,那就需要给identify()和speak()显式传入一个上下文对象。

误解

有两种常见的对于this的解释,但是它们都是错误的。

1、指向自身

人们很容易把this理解成指向函数自身。

那么为什么需要从函数内部引用函数自身呢?常见的原因是递归(从函数内部调用这个函数)或者可以写一个在第一次被调用后自己解除绑定的事件处理器。

看下面这段代码,思考foo会被调用了多少次?

function foo(num) {
  console.log('foo: ' + num);

  // 记录foo被调用的次数
  this.count++;
}

foo.count = 0;

var i;
for (i = 0; i < 10; i++) {
  if (i > 5) {
    foo(i);
  }
}

// foo被调用了多少次?
console.log(foo.count);

打印结果:

2-2.png

console.log语句产生了4条输出,证明foo(..)确实被调用了4次,但是foo.count仍然是0。显然从字面意思来理解this是错误的。

执行foo.count = 0时,的确向函数对象foo添加了一个属性count。但是函数内部代码this.count中的this并不是指向那个函数对象。

2、它的作用域

第二种常见的误解是,this指向函数的作用域。这个问题有点复杂,因为在某种情况下它是正确的,但是在其他情况下它却是错误的。

this在任何情况下都不指向函数的词法作用域。

function foo() {
  var a = 2;
  this.bar();
}

function bar() {
  console.log(this.a);
}

foo();

直接打印上面的代码会得到一个报错:

2-3.png

这段代码试图通过this.bar()来引用bar()函数。这是不可能实现的,使用this不可能在词法作用域中查到什么。

每当开发者想要把this和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。

this到底是什么

this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this全面解析

调用位置

在理解this的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

寻找调用位置就是寻找“函数被调用的位置”。最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。调用位置就在当前正在执行的函数的前一个调用中。

通过下面的代码来看什么是调用栈和调用位置:

function baz() {
  // 当前调用栈是:baz
  // 因此,当前调用位置是全局作用域

  console.log('baz');
  bar(); // <-- bar的调用位置
}

function bar() {
  // 当前调用栈是baz -> bar
  // 因此,当前调用位置在baz中

  console.log('bar');
  foo(); // <-- foo的调用位置
}

function foo() {
  // 当前调用栈是baz -> bar -> foo
  // 因此,当前调用位置在bar中

  console.log('foo');
}

baz(); // <-- baz的调用位置

打印的结果如下:

2-4.png

绑定规则

来看看在函数的执行过程中调用位置如何决定this的绑定对象。

首先必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

充分理解四条规则之后,再理解多条规则都可用时它们的优先级如何排列。

1、默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。

var a = 2;

function foo() {
  console.log(this.a);
}

foo(); // 2

打印结果是2。也就是当调用foo()时,this.a被解析成了全局变量a。函数调用时应用了this的默认绑定,因此this指向全局对象。

2、隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,不过这种说法可能会造成一些误导。

思考下面的代码:

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo,
};

obj.foo(); // 2

当foo()被调用时,它的前面确实加上了对obj的引用。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。

3、显式绑定

JavaScript提供的绝大多数函数以及你自己创建的所有函数都可以使用call(..)和apply(..)方法。

它们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。

因为可以直接指定this的绑定对象,因此我们称之为显式绑定。

思考下面的代码:

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
};

foo.call(obj); // 2

通过foo.call(..),我们可以在调用foo时强制把它的this绑定到obj上。

4、new绑定

在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用new初始化类时会调用类中的构造函数。通常的形式是这样的:

something = new MyClass(..);

在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。

优先级

1、四条规则的优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

2、判断this

可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:

(1)函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

var bar = new foo();

(2)函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

var bar = foo.call(obj2);

(3)函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

var bar = obj1.foo();

(4)如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

var bar = foo();

绑定例外

在某些场景下this的绑定行为会出乎意料,你认为应当应用其他绑定规则时,实际上应用的可能是默认绑定规则。

被忽略的this

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:

function foo() {
  console.log(this.a);
}

var a = 2;

foo.call(null); // 2

那么什么情况下会传入null呢?

一种非常常见的做法是使用apply(..)来“展开”一个数组,并当作参数传入一个函数。类似地,bind(..)可以对参数进行柯里化(预先设置一些参数),这种方法有时非常有用。

间接引用

另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。

间接引用最容易在赋值时发生:

function foo() {
  console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2

赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。根据我们之前说过的,这里会应用默认绑定。

软绑定

如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。

function foo() {
  console.log('name: ' + this.name);
}

var obj = { name: 'obj' },
  obj2 = { name: 'obj2' },
  obj3 = { name: 'obj3' };

var fooOBJ = foo.softBind(obj);

fooOBJ(); // name: obj

obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!! !

fooOBJ.call(obj3); // name: obj3 <---- 看!

setTimeout(obj2.foo, 10);
// name: obj   <---- 应用了软绑定

可以看到,软绑定版本的foo()可以手动将this绑定到obj2或者obj3上,但如果应用默认绑定,则会将this绑定到obj。

this词法

ES6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

箭头函数的词法作用域:

function foo() {
  // 返回一个箭头函数
  return a => {
    //this继承自foo()
    console.log(this.a);
  };
}

var obj1 = {
  a: 2,
};

var obj2 = {
  a: 3,
};

var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3!

foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。

总结

我们来总结一下本篇的主要内容:

  • this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

  • 如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。

  • ES6中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前代码中的self = this机制一样。

文章转载自:华为云开发者联盟

原文链接:https://www.cnblogs.com/huaweiyun/p/18096240

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年第16届大广赛新命题发布-爱华仕箱包

2024年3月27日&#xff0c;2024年第16届大广赛发布了新的命题&#xff0c;爱华仕箱包命题&#xff0c;自2017年起&#xff0c;爱华仕箱包已连续8年担任全国大学生广告艺术大赛命题单位。 爱华仕现已实现百货、超市、电商、礼品、投标、海外市场6大零售网络的全覆盖&#xff0c…

云原生最佳实践系列 4:基于 MSE 和 SAE 的微服务部署与压测

方案概述 云原生应用平台为基于 Spring Cloud / Dubbo 开发的微服务应用提供了完善的能力支撑&#xff0c;例如服务注册发现、Serverless 无服务部署、实例弹性伸缩、微服务链路跟踪、全链路压力测试等&#xff0c;应用能够方便快捷的部署在阿里云上。 阿里云原生产品完全兼容…

电脑windows 蓝屏【恢复—无法加载操作系统,原因是关键系统驱动程序丢失或包含错误。.......】

当你碰到下图这种情况的电脑蓝屏&#xff0c;先别急着重装系统&#xff0c;小编本来也是想重装系统的&#xff0c;但是太麻烦&#xff0c;重装系统后你还得重装各种软件&#xff0c;太麻烦了&#xff01;&#xff01; 这种情况下&#xff0c;你就拿出你的启动U盘&#xff0c;进…

每日一题 --- 删除链表的倒数第 N 个结点[力扣][Go]

删除链表的倒数第 N 个结点 题目&#xff1a;19. 删除链表的倒数第 N 个结点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#x…

我的创作纪念日——1024天从坚持到享受的遥感云计算技术分享之路

机缘 在CSDN进行开始进行创作的缘由是因为我开始进行GEE的学习&#xff0c;通过搜索和记录慢慢实现了自己从初学者到遥感领域云计算的优质创作者&#xff0c;一步步从一个需要别人为自己解决问题的小白逐渐成为了一个为广大科研人员&#xff08;高校学生和老师&#xff09;提供…

【WiFi】WiFi QoS映射关系及抓包分析

WiFi Aliance认证测试对应图 RFC8325 ​​​​​​RFC 8325https://datatracker.ietf.org/doc/html/rfc8325 RFC 8325 – WiFi QoS Mappings | mrn-cciew (mrncciew.com)https://mrncciew.com/2021/09/14/rfc-8325-wifi-qos-mappings/ 802.11 UP和DSCP映射关系 802.11 UP …

get 请求中传递数组参数

文章目录 问题分析 问题 使用get请求传参时有参数是数组 分析 qs.stringify({ a: [b, c] }, { arrayFormat: indices }) // 输出结果&#xff1a;a[0]b&a[1]c qs.stringify({ a: [b, c] }, { arrayFormat: brackets }) // 输出结果&#xff1a;a[]b&a[]c qs.stringif…

【2G 50元/年 4G 618/3年!】支持比价必赔 送抽奖机会 京东云服务器推荐 附阿里云 腾讯云价格对比表

《最新对比表》已更新在文章头部—腾讯云文档&#xff0c;文章具有时效性&#xff0c;请以腾讯文档为准&#xff01; 【腾讯文档实时更新】云服务器1分钟教会你如何选择教程 https://docs.qq.com/document/DV0RCS0lGeHdMTFFV?tab000003 ​ 当前活动&#xff1a;采购季&#…

IDEA : 已经有一个永久破解版的IDEA2019版本,现在又想安装最新版本的,俩版本共存,发现新版本打不开的解决方案

在新文件的目录下&#xff0c;注释掉一行19版本的地址 地址&#xff1a;C:\Users\23999\AppData\Roaming\JetBrains\IntelliJIdea2023.2 (不同电脑Users后边的一个地址的注释会不一样) 然后找到该目录下的indea64.exe.vmoptions 用 记事本 打开 在-javaagent 那一栏里会自动给…

AWS EC2设置root登录

在使用亚马逊的服务器时&#xff0c;官方默认是使用密钥登录&#xff0c;跟国内的云服务器差别较大&#xff0c;本文记录下&#xff0c;如何开放AWS EC2的root登录。 一、通过网页版或者XShell登录服务器 这里略过 二、设置root账户密码 # 切换 root sudo -i # 设置或修改密…

产品推荐 | 基于 ALINX XILINX ZYNQ-7000 XC7Z020 多网口 FPGA开发板

01、产品概述 此款开发板使用的是Xilinx公司的Zynq 7000系列的芯片&#xff0c;型号为XC7Z020-2CLG484I&#xff0c;5 路千兆以太网接口&#xff0c;支持多网口高速数据交换数据处理存储&#xff0c;视频传输处理以及工业控制等。内核 CPU搭载双核 ARM CORTEX-A9&#xff1b;在…

leetcode669. 修剪二叉搜索树

1.错误思路; 我原来的思路是前序遍历 void dfs(TreeNode* root,TreeNode* node,int low,int high){if(node){if(node->val<low||node->val>high){remove(root,node->val);}dfs(root,node->left,low,high);dfs(root,node->right,low,high);}}挨个删除不符…

奥比中光深度相机(一):环境配置

文章目录 奥比中光深度相机&#xff08;一&#xff09;&#xff1a;环境配置简介电脑环境SDK配置步骤安装环境依赖填写路径&#xff0c;点击Configure选择Visual studio点击Generate完成基于Python的SDK配置方法一&#xff1a;使用Cmake直接打开方法二&#xff1a;通过源文件打…

代码随想录算法训练营DAY7| C++哈希表Part.2|LeetCode:454.四数相加II、383.赎金信、15. 三数之和、18.四数之和

文章目录 454.四数相加II思路C代码 383.赎金信C 代码 15. 三数之和排序哈希法思路C代码 排序双指针法思路去重C代码 18.四数之和前言剪枝C代码 454.四数相加II 力扣题目链接 文章链接&#xff1a;454.四数相加II 视频链接&#xff1a;学透哈希表&#xff0c;map使用有技巧&…

3D开发工具HOOPS更新:高效、轻量化模型处理再突破!

随着数字化转型的深入发展&#xff0c;高性能图形显示成为了软件开发领域的重要研究方向。在众多工具和库中&#xff0c;HOOPS因其强大的三维图形处理能力而受到广泛关注。 HOOPS也与时俱进&#xff0c;持续更进与创新&#xff0c;近期又推出了一系列新功能&#xff0c;这些功…

Qt篇——Qt无法翻译tr()里面的字符串

最近遇到使用Qt语言家翻译功能时&#xff0c;ui界面中的中文都能够翻译成英文&#xff0c;但是tr("测试")这种动态设置给控件的中文&#xff0c;无法翻译&#xff08;lang_English.ts文件中的翻译已经正确添加了tr()字符串的翻译&#xff09;。 上网搜了很多资料&am…

STM32学习笔记(7_1)- ADC模数转换器

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 本期开…

23届嵌入式被裁,有什么好的就业建议?

最近看到了一个提问&#xff0c;原话如下&#xff1a; 本人23届毕业生&#xff0c;就业方向嵌入式软件&#xff0c;坐标深圳&#xff0c;工作3月公司裁员&#xff0c;目前接近12月开始找工作。 boss上投递简历&#xff0c;校招岗&#xff0c;比较有规模的好公司基本已读不回&am…

PCL拟合并绘制平面(二)

使用RANSAC拟合点云平面 1、C实现2、效果图 普通的点云平面拟合方式在一般情况下可以得到较好的平面拟合效果&#xff0c;但是容易出现平面拟合错误或是拟合的平面不是最优的情况。此时就需要根据自己的实际使用情况&#xff0c;调整平面拟合的迭代次数以及收敛条件。 使用RAN…

PHPCMS v9城市分站插件

PHPCMS自带的有多站点功能&#xff0c;但是用过的朋友都知道&#xff0c;自带的多站点功能有很多的不方便之处&#xff0c;例如站点栏目没法公用&#xff0c;每个站点都需要创建模型、每个站点都需要单独添加内容&#xff0c;还有站点必须静态化。如果你内容很多这些功能当然无…