JS的this关键字详解

引言

学习JS的this关键字往往难以理解和应用,本文详细解读JS中的this关键字,并结合案例给出相应的解释。

PS: https://github.com/WeiXiao-Hyy/blog整理了后端开发的知识网络,欢迎Star!

JS中的this关键字

this提供了一种更优雅的方式来隐式“传递”一个对象的引用,因此可以将API设计得更加简洁并且易于复用。

this的作用域

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

function foo() {var a = 2;this.bar();
}function bar() {console.log(this.a);
}foo(); // ReferenceError: a is not defined

上述代码,看完本文之后再来思考原因。

绑定规则

默认绑定

思考下面的代码

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

当调用foo()时,this.a被解析成了全局变量a。但是如果使用strict mode,则不能将全局对象用于默认绑定,因此this会绑定到undefined。

隐式绑定

function foo() {console.log(this.a);
}var obj = {a: 2,foo: foo
};obj.foo(); // 2 

当foo()被obj调用时,则上下文则绑定到了obj对象中,即this.a=2;

function foo() {console.log(this.a);
}var obj2 = {a: 42,foo: foo
};var obj1 = {a: 2,obj2: obj2
};obj1.obj2.foo(); // 42

同时注意对象属性引用链只有上一层中起作用,即this.a=42;

隐式丢失

function foo() {console.log(this.a);
}var obj = {a: 2,foo: foo
};var bar = obj.foo; // 函数别名!var a = "oops, global"; // a是全局对象的属性bar(); // "oops, global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

一种更微妙,更常见并且更出乎意料的情况发生在传入回调函数时:

function foo() {console.log(this.a);
}function doFoo(fn) {// fn其实引用的是foofn(); // <-- 调用位置!
}var obj = {a: 2,foo: foo
};var a = "oops, global"; // a是全局对象的属性doFoo(obj.foo); // "oops, global"

其实上述案例就可以解释setTimeout()函数中this的绑定问题:

function setTimeout(fn, delay) {// 等待delay毫秒fn(); // <-- 调用位置!
}

显式绑定

当然可以使用函数的call和apply方法来进行显式绑定。如下代码在调用foo时强制将this绑定到obj上。

function foo() {console.log(this.a);
}var obj = {a:2
};foo.call(obj); // 2

从this绑定的角度来说,call和apply是一样的,它们的区别体现在其它参数上。

硬绑定

硬绑定的典型应用场景就是创建一个包裹函数,负责接受参数并返回值:

function foo() {console.log(this.a);
}var obj = {a:2
};var bar = function() {foo.call(obj);
};bar(); // 2
setTimeout(bar, 100); // 2// 硬绑定的bar不可能再修改它的this
bar.call(window); // 2

上述绑定是一种显式的强制绑定,无法改变其this指向。同时ES5提供了内置的方法Function.prototype.bind,用法如下:

function foo(something) {console.log(this.a, something);return this.a + something;
}var obj = {a:2
};var bar = foo.bind(obj);var b = bar(3); // 2 3
console.log(b); // 5

API调用的上下文

在第三方库函数,以及JS许多新的内置函数中,都提供了一个可选的参数,通常被称为上下文(Context),比如forEach()函数。

function foo(el) {console.log(el, this.id);
}var obj = {id: "awesome"
};// 调用foo(..)时把this绑定到obj
[1, 2, 3].forEach(foo, obj);
// 1 awesome 2 awesome 3 awesome

new绑定

首先需要明确的是JS的new和其他面向对象语言的new含义不太一样。JS中只是被new调用的普通函数而已。考虑以下代码:

function foo(a) {this.a = a;
}var bar = new foo(2);
console.log(bar.a); // 2

使用new来调用foo()时,会构造一个新对象并把它绑定到foo()调用中的this上。

优先级

直接说结论,其实也很显然:new>显式绑定>隐式绑定>默认绑定。

  1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
  2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

被忽略的this

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:一种常见的做法是使用apply(…)来展开一个数组,并当作参数传入一个函数。

function foo(a, b) {
console.log("a:" + a + ", b:" + b);
}// 把数组“展开”成参数
foo.apply(null, [2, 3]); // a:2, b:3// 使用bind(..)进行柯里化
var bar = foo.bind(null, 2);
bar(3); // a:2, b:3

然而,总是使用null来忽略this绑定可能产生一些副作用。如果某个函数确实使用了this,那么默认绑定规则会把this绑定到全局对象。

更安全的this

一种更加安全的做法是传入一个特殊的对象,使用Object.create(null)创建一个空对象。

function foo(a, b) {console.log("a:" + a + ", b:" + b);
}// 空对象
var ø = Object.create(null);// 把数组展开成参数
foo.apply(ø, [2, 3]); // a:2, b:3// 使用bind(..)进行柯里化
var bar = foo.bind(ø, 2);
bar(3); // a:2, b:3

箭头函数this

箭头函数不使用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。

参考资料

  • 你不知道的JavaScript(上卷)

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

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

相关文章

虚拟机与主机的网络桥接

虚拟机网路桥接是一种网络配置方式&#xff0c;它允许虚拟机与物理网络中的其他设备直接通信。在桥接模式下&#xff0c;虚拟机的网络接口通过主机的物理网卡连接到局域网中&#xff0c;就像主机本身一样&#xff0c;拥有自己的MAC地址和IP地址。这种方式使得虚拟机可以像独立的…

【SpringBoot Web框架实战教程(开源)】01 使用 pom 方式创建 SpringBoot 第一个项目

导读 这是一系列关于 SpringBoot Web框架实战 的教程&#xff0c;从项目的创建&#xff0c;到一个完整的 web 框架&#xff08;包括异常处理、拦截器、context 上下文等&#xff09;&#xff1b;从0开始&#xff0c;到一个可以直接运用在生产环境中的web框架。而且所有源码均开…

MySQL进阶-索引-使用规则-最左前缀法则和范围查询

文章目录 1、最左前缀法则2、启动mysql3、查询tb_user4、查看tb_user的索引5、执行计划 profession 软件工程 and age31 and status 06、执行计划 profession 软件工程 and age317、执行计划 profession 软件工程8、执行计划 age31 and status 09、执行计划 status 010、执行…

方法的其他形式——方法使用时常见的问题

示例&#xff1a; public class MethodDemo02 {public static void main(String[] args) {//目标&#xff1a;掌握按照方法的实际业务需求不同&#xff0c;设计出合理的方法形式来解决问题//需求&#xff1a;打印三行Hello World.printfHelloWorld();System.out.println("…

python 字符串内置函数

序号函数作用1len()返回字符串的长度2find()查找字符串中指定子字符串的第一个出现的位置3rfind()查找字符串中指定子字符串的最后一个出现的位置4index()查找字符串中指定子字符串的第一个出现的位置&#xff0c;如果不存在则报错&#xff0c;提示异常。5rindex()查找字符串中…

dma是什么意思?什么是dma?

DMA有两种主要的含义&#xff0c;以下是针对这两种含义的详细解释&#xff1a; 一、DMA&#xff08;Dynamic Mechanical Analysis&#xff09; 定义&#xff1a; DMA&#xff0c;全称Dynamic Mechanical Analysis&#xff0c;即动态热机械分析。这是一种用于测量黏弹性材料的…

k8s持久化之emptyDir使用

目录 概述实践代码 概述 理解emptyDir使用&#xff0c;是后续k8s持久化进阶&#xff0c;高阶使用的基础。 实践 代码 详细说明在代码中 # 缓存数据&#xff0c;可以让多个容器共享数据 # 删除 Pod 时&#xff0c;emptyDir 数据同步消失 # 定义 initContainer -> 下载数据…

计组--输入输出系统--复习

文章目录 前言一、概述二、I/O接口三、主机和外设交换信息的方式四、中断系统总结 前言 学无止境&#xff0c;笔勤不辍。今晚加班&#xff0c;再赶一章…有关计组的输入输出系统相关的知识点… 一、概述 外设特点:1.数据传输速度相差较大 2.工作时有独立性&#xff0c;具有自…

K210视觉识别模块学习笔记6: 识别苹果_图形化操作函数_

今日开始学习K210视觉识别模块: 图形化操作函数 亚博智能 K210视觉识别模块...... 固件库: canmv_yahboom_v2.1.1.bin 训练网站: 嘉楠开发者社区 今日学习如何在识别到目标的时候添加图形化操作:(获取坐标、框出目标等) 在识别苹果的基础上 学习与添加 这些操…

C# 入门—实现 Hello, World!

目录 一、.net 平台 二、.net 都能干什么&#xff1f; 三、.net 两种交互模式 四、使用 VS Code 开发 C# 程序 五、实现 Hello, World! 一、.net 平台 下载 .NET(Linux、macOS 和 Windows) (microsoft.com) .NET 简介 - .NET | Microsoft Learn C# :一种编程语言,可以开…

未来出行新选择——加油宝APP,让您的每一次加油都充满智慧与便捷!

一、前言 随着科技的飞速发展&#xff0c;智能手机已经成为我们生活中不可或缺的一部分。为了满足广大车主对便捷、高效加油服务的需求&#xff0c;我们倾力打造了全新的加油宝APP。这款APP不仅为您提供一站式的加油服务&#xff0c;还融合了多项创新功能&#xff0c;让您的出…

从0开始C++(十一):智能指针

目录 概念 作用 auto_ptr(自动指针) unique_ptr(唯一指针) shared_ptr(共享指针) weak_ptr(虚指针) 补充&#xff1a;手写一个共享指针类 概念 C的智能指针是一种用于管理动态分配内存的指针。它是C语言的一个重要特性&#xff0c;通过自动管理内存资源&#xff0c;帮助开…

C语言入门系列:特殊的main函数和exit函数

文章目录 一&#xff0c;main函数二&#xff0c;exit函数1&#xff0c;exit函数2&#xff0c;atexit()函数2.1 atexit函数的简介2.2 atexit注册的函数一定会被调用吗2.2.1 正常退出测试2.2.2 异常退出测试 一&#xff0c;main函数 一个C程序至少包含一个函数&#xff0c;这个函…

机器学习Python代码实战(二)分类算法:k-最近邻

一.k-最近邻算法步骤 1.选择适当的k值。它表示在预测新的数据点时要考虑的邻居数量。 2.计算距离。计算未知点与其他所有点之间的距离。常用的距离计算方法主要有欧氏距离&#xff0c;曼哈顿距离等。 3.选择邻居。在训练集中选择与要预测的数据点距离最近的k个邻居。 4.预测…

利用浏览器DevTools中对React项目进行内存泄露排查

利用浏览器DevTools中对React项目进行内存泄露排查 场景&#xff1a;用户在某个页面操作时&#xff0c;在监控平台收集到的数据表现为内存占用有逐步提高的趋势&#xff0c;最先想到的是 DOM 元素卸载后其 JavaScript 对象未能被垃圾回收这类内存泄漏问题。同时&#xff0c;如…

解决 macOS 中“无法验证开发者”的问题

解决 macOS 中“无法验证开发者”的问题 在使用 macOS 系统时&#xff0c;你可能会遇到一个常见的问题&#xff1a;当你尝试安装或打开某些应用程序时&#xff0c;系统会弹出一个警告&#xff0c;提示“无法验证开发者”。这通常发生在从非官方 App Store 下载的应用程序上。本…

随机步问题

随机步问题 1.题目简介2.题目分析3.创建变量4.主程序5.程序效果6.程序可以改进的点 1.题目简介 2.题目分析 数组初始化 生成随机方向 判断程序结束的标志 当前元素为Z&#xff0c;或者四个方向都堵住了 3.创建变量 arry[ROW][COL]创建二维数组 _Bool a,b,c,d判断是否会出现四…

(新)Spring Security如何自定义失败处理器

&#xff08;直接从三、实战开始看&#xff09; &#xff08;直接从三、实战开始看&#xff09; &#xff08;直接从三、实战开始看&#xff09;可点击&#xff1a; &#xff08;新&#xff09;Spring Security如何自定义失败处理器-CSDN博客 我们还希望在认证失败或者是授…

ChatGPT 的原理简介

人工智能&#xff08;AI&#xff09;在过去的几十年里取得了巨大的进步&#xff0c;其中一种令人瞩目的应用就是聊天机器人。ChatGPT 就是这样一款通过自然语言处理与用户进行对话的 AI 工具。它是基于 OpenAI 的 GPT&#xff08;Generative Pre-trained Transformer&#xff0…

ESP32 双线汽车接口 (TWAI)

一&#xff1a;TWAI概述 双线汽车接口 (TWAI) 是一种适用于汽车和工业应用的实时串行通信协议。它兼容 ISO11898-1 经典帧&#xff08;CAN2.0&#xff09;&#xff0c;因此可以支持标准帧格式&#xff08;11 位 ID&#xff09;和扩展帧格式&#xff08;29 位 ID&#x…