深入剖析JavaScript中的this(上)
在Javascript中,this 关键字是一个非常重要的概念,this这个关键字可以说是很常见也用的很多,说它简单也很简单,说它难也很难。我们经常会用到this,也经常会因为this头疼,是一个经常被误解和误用的概念,为什么呢,因为有时候我们不知道this到底指的是什么?怎么用?
在 JavaScript 中,this 的值在函数被调用时确定,而不是在函数被创建时确定。这使得 this 在 JavaScript 中的行为与其他一些语言中的类似关键字(如 Python 的 self 或 Java 的 this)有所不同。本文将从全局作用域或函数外部、普通函数调用、对象的方法、构造函数、事件处理函数、箭头函数几个方面来剖析JavaScript中的this。
一、抛砖引玉
先看一段代码:
var name = "前端技术营";
var obj = {name: "张三",foo: function() {console.log(this.name);}
};
var foo = obj.foo;
obj.foo(); // 张三
foo(); // 前端技术营
可以看到上面代码中,obj.foo() 和 foo() 都指向同一个函数,但是执行结果却不一样;产生这种差异的原因,就在于函数体内部使用了 this 关键字。
在《JavaScript高级程序设计》一书中是这样说的,this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常纸箱window。
所以上面的问题,对obj.foo() 来说, foo 运行在 obj 环境中,所以 this 指向 obj ;对于 foo() 来说, foo 运行在全局环境下,所以在非严格模式下 this 指向 window ,所以导致了两者运行的结果不同。看到这有的人可能就有疑问了,函数的运行环境是如何判定的?为什么 obj.foo() 就是在 obj 环境,为何 var foo = obj.foo; foo() 就在全局环境执行了?插个眼,继续往下看,就明白这个问题了!
二、为什么需要this
先看下面代码:
function foo() {console.log(this.name)
}var bar = {name: '张三',foo: foo
}
var baz = {name: '李四',foo: foo
}
bar.foo(); // 张三
baz.foo(); // 李四
Javascript 引擎在处理上面代码时,会在堆内存中,生成两个对象,然后把这两个对象在内存中的地址分别赋值给变量bar和baz。在读取 this.name 时,需要先从变量bar和baz拿到地址,然后再分别从对应地址中拿到对象,再返回它的 name 属性。
对象的属性是一个函数,当引擎遇到对象属性是函数的情况,会将函数单独保存在堆中,然后再将函数的地址赋值给对象属性,而 Javascript 是允许在函数体内引用当前环境的其他变量。那么问题来了,函数可以在不同的运行环境执行,所以我们就需要一种机制,能够在函数内获得当前运行环境,foo只定义了一次,却可以被不同的对象引用,实现了代码共享,由此诞生了 this,它的设计目的就是指向函数运行时所在的环境。
那么,如何正确的在代码中判定this所指向的环境呢?
三、全局作用域中
在全局作用域代码中this 是不变的,this始终是全局对象本身,即window。
var a = '张三';
this.b = '李四';
window.c = '王五';console.log(this.a); // 张三
console.log(b); // 李四
console.log(this.c); // 王五console.log(this === window); // true
运行以上代码发现,this === window为true,也就是说在全局作用域中this就是全局对象window,所以上述 a ,b ,c 都相当于在全局对象上添加相应的属性。
var a = 1;
var b = function () {return "function1";
}
console.log(window.a); //1
console.log(window.b); //ƒ (){ return "function1"; }
console.log(window.a === a); //true
console.log(window.b === b); //true
在全局对象上定义的变量可以直接访问。
window.aa = 2;
this.bb = function () {return "function2";
}
console.log(aa); //2
console.log(bb); //ƒ (){ return "function2"; }
四、函数中的this
在函数中使用this,才是令我们最容易困惑的,这里我们主要是对函数代码中的this进行分析。
这里再次强调一下,this的指向在函数创建的时候是决定不了的,而是在进入当前执行上下文时确定的,也就是在函数执行时并且是执行前确定的。但是同一个函数,作用域中的this指向可能完全不同,但是不管怎样,函数在运行时的this的指向是不变的,而且不能被赋值。
函数中this的指向丰富的多,它可以是全局对象、当前对象、或者是任意对象,当然这取决于函数的调用方式。在JavaScript中函数的调用方式有一下几种方式:作为函数调用、作为对象属性调用、作为构造函数调用、使用apply或call调用。下面我们将按照这几种调用方式一一讨论this的含义。
4.1 作为函数调用
看如下代码:
function foo() {var name = '张三';console.log(this.name); // undefinedconsole.log(this); // Window
}foo();
按照我们上面说的this最终指向的是调用它的对象,这里的函数foo实际是被Window对象所点出来的,下面的代码就可以证明。
function foo() {var name = '张三';console.log(this.name); // undefinedconsole.log(this); // Window
}window.foo();
和上面代码一样,其实alert也是window的一个属性,也是window点出来的。
function foo() {function bar() {this.name = '张三';console.log(this === window); // true}bar()
}
foo();
console.log(name); // 张三
上述代码中,在函数内部的函数独立调用,此时this还是被绑定到了window。
在严格模式下,不能将全局对象 window 作为默认绑定,此时 this 会绑定到 undefined ,但是在严格模式下调用函数则不会影响默认绑定。
function foo() {"use strict";console.log(this===window); // falseconsole.log(this===undefined); // true
}
foo();"use strict"
function foo() {var name = "张三";console.log(this.name);
};foo();
// Uncaught TypeError: Cannot read property ‘name’ of undefined at foo
加了"use strict"之后,和上面一样的代码运行就会报错,在严格模式下,不能将全局对象 window 作为默认绑定。
var name = '张三';function foo() {console.log(this.name); // 张三console.log(this === window); // true
};(() => {"use strict"foo();
})();
看上面代码,在foo() 前加了"use strict",运行并没有报错,依然打印出了结果,可见在严格模式下调用函数则不会影响默认绑定。
小结:当函数作为独立函数被调用时,内部this被默认绑定为(指向)全局对象window,但是在严格模式下会有区别,在严格模式下this被绑定为undefined。
4.2 作为对象属性调用
先看一段代码:
var obj = {name: "张三",foo: function() {console.log(this.name); //张三}
}
obj.foo();
根据this最终指向调用它的对象可知,这里的this指向的是对象obj,因为调用这个foo是通过obj.foo()执行的,那自然指向就是对象o。
是不是感觉自己懂了?别急,再看看下边的代码。
var obj = {name: "张三",foo: function() {console.log(this.name); // 张三}
}
window.obj.foo();
先解释一下window.obj.foo(),window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点obj对象。
再看这段代码和上面的那段代码几乎是一样的,但是这里的this为什么不是指向window?如果按照上面的理论,最终this指向的是调用它的对象,那这个理论还成不成立呢,我们先接着再看下面一段代码。
var obj = {name: '张三',bar: {name: '李四',foo: function() {console.log(this.name); // 李四}}
}
obj.bar.foo();
这里同样也是对象obj点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来补充一句话,我相信你就可以彻底的理解this的指向的问题。
1、如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window。
2、如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
3、如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象。
var obj = {name: '张三',bar: {// name: '李四',foo: function() {console.log(this.name); // undefined}}
}
obj.bar.foo();
这段代码尽管对象bar中没有属性name,这个this指向的也是对象bar,因为this只会指向它的上一级对象,不管这个对象中有没有this要的属性。
var obj = {name: '张三',bar: {age: 18,foo: function() {console.log(this.age); // undefinedconsole.log(this); // window}}
}var fn = obj.bar.foo;
fn();
解释:obj.bar.foo方法声明部分,只需要理解为在堆内存中开辟了一块空间,并由obj.bar.foo持有这块内存空间的引用,由于函数尚未执行,因此还没有确定this。将obj.foo赋值给bar,也就是将函数的引用拷贝一份给了bar,bar独立调用,因此this指向window。this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的。
4.3 使用apply或call调用
apply和call为函数原型上的方法。它可以更改函数内部this的指向。
var name = '前端技术营';function foo() {console.log(this.name);
}
var obj1 = {name: '张三'
}
var obj2 = {name: '李四'
}
var obj3 = {name: '王五'
}
// this指向window,打印“前端技术营”
foo();
// this指向 obj1,打印“张三”
foo.apply(obj1);
// this指向 obj2,打印“李四”
foo.call(obj2);
// this指向 obj3,打印“王五”
foo.call(obj3);
当函数foo 作为独立函数调用时,this被绑定到了全局对象window,当使用bind、call或者apply方法调用时,this 被分别绑定到了不同的对象。
call和apply 的功能一样,唯一不同的是传给函数的参数的方式,第一个参数是this指向的新对象,从第二个参数开始,apply传数组,这个数组包含函数所需要的参数,apply只支持传入一个数组,哪怕是一个参数也要是数组形式,最终调用函数时候这个数组会拆分成一个个参数分别传入;call 直接传参数,多个参数逗号分割。
var obj1 = {name: '张三'
}
var obj2 = {name: '李四'
}function foo(arg1, arg2) {console.log(this);console.log(arg1 + arg2);
};foo.call(obj1, 1, 2);
// {name: '张三'}
// 3foo.apply(obj2, [1, 2]);
// {name: '李四'}
// 3
还有一个bind方法。bind方法和call使用方式一样,作用也一样,不一样的是实现方式,call和apply传参结束后直接执行函数,而bind只是更改this值和给函数传参,函数并不执行,所以bind可以作为事件的处理函数去使用。
var name = '前端技术营';function foo() {console.log(this.name);
}
var obj = {name: '张三'
}foo.bind(obj);
console.log(foo.bind(obj))
// ƒ foo() { console.log(this.name); }foo.bind(obj)(); // 张三
function add(a, b){return a + b
}
function sub(a, b){return a - b
}
add.bind(sub, 5, 3)(); // 8
4.4 作为构造函数调用
var name = '前端技术营';function Foo(){this.name = "张三";
}
var baz = new Foo();
console.log(baz.name); // 张三
这里之所以对象baz可以点出函数Foo里面的name是因为new关键字可以改变this的指向,将这个this指向对象baz(因为用了new关键字就是创建一个对象实例),这里用变量baz创建了一个Foo的实例(相当于复制了一份Foo到对象baz里面),此时仅仅只是创建,并没有执行,而调用这个函数Foo的是对象baz,那么this指向的自然是对象baz。那么为什么对象baz中会有name,因为你已经复制了一份Foo函数到对象baz中,用了new关键字就等同于复制了一份。
4.5 总结
当我们要判断当前函数内部的this绑定,可以依照下面的原则:
(1)函数是否在是通过 new 操作符调用?如果是,this 绑定为新创建的对象。
var bar = new foo(); // this指向bar
(2)函数是否通过call或者apply调用?如果是,this 绑定为指定的对象
foo.call(obj1); // this指向obj1
foo.apply(obj2); // this指向obj2
(3)函数是否通过 对象 . 方法调用?如果是,this 绑定为当前对象
obj.foo(); // this指向obj
(4)函数是否独立调用?如果是,this 绑定为全局对象。
foo(); // this指向window
相关文章:
深入剖析JavaScript中的this(上)
在Javascript中,this 关键字是一个非常重要的概念,this这个关键字可以说是很常见也用的很多,说它简单也很简单,说它难也很难。我们经常会用到this,也经常会因为this头疼,是一个经常被误解和误用的概念&…...
Junit深入讲解(JAVA单元测试框架)
1、此处用的是Junit5,此处pom文件需要引的依赖是 <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.1</version><scope>test</scope></depende…...
Spring boot如何执行单元测试?
Spring Boot 提供了丰富的测试功能,主要由以下两个模块组成: spring-boot-test:提供测试核心功能。spring-boot-test-autoconfigure:提供对测试的自动配置。 Spring Boot 提供了一个 spring-boot-starter-test一站式启动器&…...
Django详细教程(一) - 基本操作
文章目录 前言一、安装Django二、创建项目1.终端创建项目2.Pycharm创建项目(专业版才可以)3.默认文件介绍 三、创建app1.app介绍2.默认文件介绍 四、快速上手1.写一个网页步骤1:注册app 【settings.py】步骤2:编写URL和视图函数对…...
Qt编译QScintilla(C++版)过程记录,报错-lqscintilla2_qt5d、libqscintilla2_qt5找不到问题解决
Qt编译QScintilla [C版] 过程记录 本文是编译该 QScintilla 组件库供 QtCreater 开发 C 桌面软件 流程记录一、编译环境 系统: Windows 10Qt:Qt 5.14.2编译套件:MinGW 64Qscintilla:QScintilla_src-2.11.6 二、下载链接 网站链…...
android QtScrcpy 共享屏幕 获取本地Address
android QtScrcpy https://gitee.com/B arryda/QtScrcpy scrcpy - 手机无线投屏到电脑 https://zhuanlan.zhihu.com/p/80264357?utm_sourcewechat_session public String getLocalIpAddress() { String ipv4; List<NetworkInterface> nilist …...
【SQL Server】1. 认识+使用
1. 创建数据库的默认存储路径 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft SQL Server 2008 R2 当我们选择删除数据库时,对应路径下的文件也就删除了 2. 导入导出数据工具的路径 3. 注册数据库遇到的问题 ??? 目前的问题就是服务器新建…...
视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】
视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构,可以在复杂的网络环境中快速、灵活部署,平台视频能力丰富,可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…...
图神经网络实战(7)——图卷积网络(Graph Convolutional Network, GCN)详解与实现
图神经网络实战(7)——图卷积网络详解与实现 0. 前言1. 图卷积层2. 比较 GCN 和 GNN2.1 数据集分析2.2 实现 GCN 架构 小结系列链接 0. 前言 图卷积网络 (Graph Convolutional Network, GCN) 架构由 Kipf 和 Welling 于 2017 年提出,其理念是…...
大话设计模式之外观模式
外观模式(Facade Pattern)是一种软件设计模式,旨在提供一个简单的接口,隐藏系统复杂性,使得客户端能够更容易地使用系统。这种模式属于结构型模式,它通过为多个子系统提供一个统一的接口,简化了…...
CAD Plant3D 2024 下载地址及安装教程
CAD Plant3D是一款专业的三维工厂设计软件,用于在工业设备和管道设计领域进行建模和绘图。它是Autodesk公司旗下的AutoCAD系列产品之一,专门针对工艺、石油、化工、电力等行业的设计和工程项目。 CAD Plant3D提供了一套丰富的工具和功能,帮助…...
Intellij IDEA / Android studio 可持续开发笔记
Intellij 的Java/安卓工具链有着一种不可持续性,这种不可持续性体现在多个方面。 首先是不可持续运行。IDEA 使用时间越长,内存占用越大,从不主动释放。运行时间越长,日志越多,从不主动清理。 然后是不完整的开源&am…...
c++----list模拟实现
目录 1. list的基本介绍 2. list的基本使用 2.1 list的构造 用法示例 2.2 list迭代器 用法示例 2.3. list容量(capacity)与访问(access) 用法示例 2.4 list modifiers 用法示例 2.5 list的迭代器失效 3.list的模拟实现 3.1…...
FastAPI+React全栈开发15 让我们构建一个展示API
Chapter03 Getting Started with FastAPI 15 Let’s Build a showcase API FastAPIReact全栈开发15 让我们构建一个展示API REST APIs are all about cycles of HTTP requests and responses, it is the engine that powers the web and is implemented in every web framew…...
list(链表)容器(二)
一、list 插入和删除 函数原型: push_back(elem);//在容器尾部加入一个元素 pop_back();//删除容器中最后一个元素 push_front(elem);//在容器开头插入一个元素 pop_front();//从容器开头移除第一个元素 insert(pos,elem);//在pos位置插elem元素的拷贝,…...
世优科技上榜2024年度《中国虚拟数字人影响力指数报告》
日前,第三期《中国虚拟数字人影响力指数报告》在中国网络视听大会上正式发布。本期《报告》由中国传媒大学媒体融合与传播国家重点实验室(以下简称“国重实验室”)、中国传媒大学数字人研究院编制,中国网络视听协会、人民日报智慧…...
【调试方法】C代码中dump中间数据的方法
一,简介 本文主要介绍,如何在C语言代码中将音频流数据进行写入文件,方便调试定位问题: 二,函数实现 按int8_t写入 #include <stdio.h>int32_t write_int8_t_data(int8_t *name, int8_t *buffer, int32_t dat…...
【BUG】vue中@change时间传值丢失问题
项目场景: 在修改项目bug时,发现后端响应到前端的值,通过change事件调用方法,在方法中拿到值时,有部分数据丢失。 问题描述 后端传到前端的值为:字符串类型的"00000089",change调用…...
Linux提权!!!
上一篇文章讲了Windows的提权,那么这篇文章就来讲一下Linux的提权 1.SUID提权 suid权限 作用:让普通用户临时拥有该文件的属主的执行权限,suid权限只能应用在二进制可执行文件(命令)上,而且suid权限只能设置…...
Android Studio学习7——常用控件view
Android控件 双击shift键——>搜索想要找的文件 Ctrlshift回车——>补全“;”号 CtrlX——>删除一行,只需把鼠标放在那一行 windows自带字体...
Springboot3 集成knife4j(swagger)
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍! 官网地址: Knife4j 集Swagger2及OpenAPI3为一体的增强解决方案. | Knife4j 本文以Springboot3版本集成kn…...
深信服:借助观测云实现全链路可观测性
导读 深信服科技股份有限公司 简称「深信服」( Sangfor Technologies Inc. ),是一家领先的网络安全和云计算解决方案提供商,致力于为全球客户提供高效、智能、安全的网络和云服务。随着公司业务的不断扩展,也面临着监…...
详解Qt中使用线程
详解Qt中使用线程 Qt中的线程相关知识涵盖了线程创建、管理、通信以及线程安全等方面。下面将详细讲解这些知识点,并提供对应的示例代码。 线程创建与管理 QThread类 Qt通过QThread类来创建和管理线程。要创建一个新的工作线程,通常有两种方法&#…...
在.Net6中用gdal实现第一个功能
目录 一、创建.NET6的控制台应用程序 二、加载Gdal插件 三、编写程序 一、创建.NET6的控制台应用程序 二、加载Gdal插件 Gdal的资源可以经过NuGet包引入。右键单击项目名称,然后选择 "Manage NuGet Packages"(管理 NuGet 包)。N…...
采用大语言模型进行查询重写——Query Rewriting via Large Language Models
文章:Query Rewriting via Large Language Models,https://arxiv.org/abs/2403.09060 摘要 查询重写是在将查询传递给查询优化器之前处理编写不良的查询的最有效技术之一。 手动重写不可扩展,因为它容易出错并且需要深厚的专业知识。 类似地…...
使用Vue实现CSS过渡和动画
01-初识动画和过渡 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>使用vue实现css过渡和动画&l…...
一家购物商场的数据运营挑战
✅作者简介:《数据运营:数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 🌸公众号:风姑娘的数字视角,免费分享数据应用相…...
React Native框架开发APP,安装免费的图标库(react-native-vector-icons)并使用详解
一、安装图标库 要使用免费的图标库,你可以使用 React Native Vector Icons 库。 首先,确保你已经安装了 react-native-vector-icons: npm install --save react-native-vector-iconsnpm install --save-dev types/react-native-vector-ic…...
idea端口占用
报错:Verify the connector‘s configuration, identify and stop any process that‘s listening on port XXXX 翻译: 原因: 解决: 一、重启大法 二、手动关闭 启动spring项目是控制台报错,详细信息如下ÿ…...
MQ消息队列详解以及MQ重复消费问题
MQ消息队列详解以及MQ重复消费问题 1、解耦2、异步调用3、流量削峰4、MQ重复消费问题,以及怎么解决?4.1、重复消费产生4.2、解决方法: https://blog.csdn.net/qq_44240587/article/details/104630567 核心的就是:解耦、异步、削锋…...
网站建设公司能信吗/新媒体seo指的是什么
该方法提交表单的方式与用户单击 Submit 按钮一样,但是表单的 onsubmit 事件句柄不会被调用。 <html><head><title>submit样式的表单</title><style>body { font: 100% 微软雅黑;}#leftblock{position:absolute;width:100px;font: 1…...
成都淮洲新城建设投资有限公司网站/网络广告电话
本文将以 kaldi 中 timit 的例程来看整个 run.sh 脚本的执行过程。 数据准备 请先进入 kaldi\egs\timit\s5\ 这个目录。 运行环境 由于 kaldi 可以在本地运行,也可以在 Oracle GridEngine 上运行,因此,请修改 cmd.sh。 如果你是在本地运行&am…...
白玉网站建设/公司做网站一般多少钱
目录 1.foreach元素的属性 2.collection属性值的三种情况 3.代码示例 3.1 collection属性值为List(Array等同): 3.2 collection属性值类型为Map: 1.foreach元素的属性 collection: foreach的对象,作为入参,对象为list、array时,collection属性值分别默认用&quo…...
wordpress默认登录页修改/百度官网认证多少钱一年
前阵子部署zabbix监控系统,做了个微信报警,下面分享下微信调API发消息的脚本。要用微信发消息,自己首先要有微信企业号,如果没有申请也容易准备工作:1.申请微信企业号2.在企业号后台创建应用3.关注微信企业号脚本用Pyt…...
一个网站怎样做两个后台/软文推广是什么
欢迎加入我们的开源流媒体服务器项目:EasyDarwin,EasyDarwin是在Apple开源流媒体服务器Darwin Streaming Serverv6.0.3)基础上进行开发和维护的免费开源、高效、易扩展的面向企业级的流媒体平台框架,EasyDarwin开始于2013年,遵循 …...
google地图嵌入网站/seminar
目录 1.浅克隆 1.1基本实现 1.2 用原型模式生成“三好学生"奖状 2.深克隆 1.浅克隆 使用场景: 对象的创建非常复杂,可以使用原型模式快捷的创建对象。 性能和安全要求比较高。 1.1基本实现 Realizetype(具体的原型类)…...