当前位置: 首页 > news >正文

深入剖析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&#xff0c;此处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 提供了丰富的测试功能&#xff0c;主要由以下两个模块组成&#xff1a; spring-boot-test&#xff1a;提供测试核心功能。spring-boot-test-autoconfigure&#xff1a;提供对测试的自动配置。 Spring Boot 提供了一个 spring-boot-starter-test一站式启动器&…...

Django详细教程(一) - 基本操作

文章目录 前言一、安装Django二、创建项目1.终端创建项目2.Pycharm创建项目&#xff08;专业版才可以&#xff09;3.默认文件介绍 三、创建app1.app介绍2.默认文件介绍 四、快速上手1.写一个网页步骤1&#xff1a;注册app 【settings.py】步骤2&#xff1a;编写URL和视图函数对…...

Qt编译QScintilla(C++版)过程记录,报错-lqscintilla2_qt5d、libqscintilla2_qt5找不到问题解决

Qt编译QScintilla [C版] 过程记录 本文是编译该 QScintilla 组件库供 QtCreater 开发 C 桌面软件 流程记录一、编译环境 系统&#xff1a; Windows 10Qt&#xff1a;Qt 5.14.2编译套件&#xff1a;MinGW 64Qscintilla&#xff1a;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 当我们选择删除数据库时&#xff0c;对应路径下的文件也就删除了 2. 导入导出数据工具的路径 3. 注册数据库遇到的问题 ??? 目前的问题就是服务器新建…...

视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…...

图神经网络实战(7)——图卷积网络(Graph Convolutional Network, GCN)详解与实现

图神经网络实战&#xff08;7&#xff09;——图卷积网络详解与实现 0. 前言1. 图卷积层2. 比较 GCN 和 GNN2.1 数据集分析2.2 实现 GCN 架构 小结系列链接 0. 前言 图卷积网络 (Graph Convolutional Network, GCN) 架构由 Kipf 和 Welling 于 2017 年提出&#xff0c;其理念是…...

大话设计模式之外观模式

外观模式&#xff08;Facade Pattern&#xff09;是一种软件设计模式&#xff0c;旨在提供一个简单的接口&#xff0c;隐藏系统复杂性&#xff0c;使得客户端能够更容易地使用系统。这种模式属于结构型模式&#xff0c;它通过为多个子系统提供一个统一的接口&#xff0c;简化了…...

CAD Plant3D 2024 下载地址及安装教程

CAD Plant3D是一款专业的三维工厂设计软件&#xff0c;用于在工业设备和管道设计领域进行建模和绘图。它是Autodesk公司旗下的AutoCAD系列产品之一&#xff0c;专门针对工艺、石油、化工、电力等行业的设计和工程项目。 CAD Plant3D提供了一套丰富的工具和功能&#xff0c;帮助…...

Intellij IDEA / Android studio 可持续开发笔记

Intellij 的Java/安卓工具链有着一种不可持续性&#xff0c;这种不可持续性体现在多个方面。 首先是不可持续运行。IDEA 使用时间越长&#xff0c;内存占用越大&#xff0c;从不主动释放。运行时间越长&#xff0c;日志越多&#xff0c;从不主动清理。 然后是不完整的开源&am…...

c++----list模拟实现

目录 1. list的基本介绍 2. list的基本使用 2.1 list的构造 用法示例 2.2 list迭代器 用法示例 2.3. list容量&#xff08;capacity&#xff09;与访问&#xff08;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 插入和删除 函数原型&#xff1a; push_back(elem);//在容器尾部加入一个元素 pop_back();//删除容器中最后一个元素 push_front(elem);//在容器开头插入一个元素 pop_front();//从容器开头移除第一个元素 insert(pos,elem);//在pos位置插elem元素的拷贝&#xff0c…...

世优科技上榜2024年度《中国虚拟数字人影响力指数报告》

日前&#xff0c;第三期《中国虚拟数字人影响力指数报告》在中国网络视听大会上正式发布。本期《报告》由中国传媒大学媒体融合与传播国家重点实验室&#xff08;以下简称“国重实验室”&#xff09;、中国传媒大学数字人研究院编制&#xff0c;中国网络视听协会、人民日报智慧…...

【调试方法】C代码中dump中间数据的方法

一&#xff0c;简介 本文主要介绍&#xff0c;如何在C语言代码中将音频流数据进行写入文件&#xff0c;方便调试定位问题&#xff1a; 二&#xff0c;函数实现 按int8_t写入 #include <stdio.h>int32_t write_int8_t_data(int8_t *name, int8_t *buffer, int32_t dat…...

【BUG】vue中@change时间传值丢失问题

项目场景&#xff1a; 在修改项目bug时&#xff0c;发现后端响应到前端的值&#xff0c;通过change事件调用方法&#xff0c;在方法中拿到值时&#xff0c;有部分数据丢失。 问题描述 后端传到前端的值为&#xff1a;字符串类型的"00000089"&#xff0c;change调用…...

Linux提权!!!

上一篇文章讲了Windows的提权&#xff0c;那么这篇文章就来讲一下Linux的提权 1.SUID提权 suid权限 作用&#xff1a;让普通用户临时拥有该文件的属主的执行权限&#xff0c;suid权限只能应用在二进制可执行文件&#xff08;命令&#xff09;上&#xff0c;而且suid权限只能设置…...

Android Studio学习7——常用控件view

Android控件 双击shift键——>搜索想要找的文件 Ctrlshift回车——>补全“&#xff1b;”号 CtrlX——>删除一行&#xff0c;只需把鼠标放在那一行 windows自带字体...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...