深入剖析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

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

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

相关文章

基于DWT(离散小波变换)的图像加密水印算法,Matlab实现

博主简介: 专注、专一于Matlab图像处理学习、交流,matlab图像代码代做/项目合作可以联系(QQ:3249726188) 个人主页:Matlab_ImagePro-CSDN博客 原则:代码均由本人编写完成,非中介,提供…

einops中的rearrange的使用方法

einops中的rearrange的使用方法 在 einops 中,rearrange 函数用于对张量进行重排操作,即重新排列张量的维度顺序或形状。它的语法如下: einops.rearrange(tensor, pattern)tensor:要重排的张量。 pattern:用于指定重排…

“继承MonoBehavior的泛型单例“本质上是一个单例模板

"继承MonoBehavior的泛型单例"本质上是一个单例模板,它可以用于管理其他所有继承自MonoBehaviour的单例类。通过继承这个泛型单例模板,可以确保每个单例类只有一个实例,并且这些实例在整个Unity应用程序中都是唯一的。这种模式使得…

华尔街基金经理为什么开始押注MVP币了?

目前,市场非常流行一种新兴的上市策略。 依靠正在被市场认可并有明显增长动力的 Meme 币,并围绕它构建一个社区,继而完成整个生态,最终,将由一系列产品完成生态的繁荣。 通过启动一个与热门 Meme 币原生集成的项目&a…

The Google File System [SOSP‘03] 论文阅读笔记

原论文:The Google File System 1. Introduction 组件故障是常态而非例外 因此,我们需要持续监控、错误检测、容错和自动恢复! 按照传统标准,文件数量巨大大多数文件都是通过添加新数据而不是覆盖现有数据来改变的,因…

大数据实验统计-1、Hadoop安装及使用;2、HDFS编程实践;3、HBase编程实践;4、MapReduce编程实践

大数据实验统计 1、Hadoop安装及使用; 一.实验内容 Hadoop安装使用: 1)在PC机上以伪分布式模式安装Hadoop; 2)访问Web界面查看Hadoop信息。 二.实验目的 1、熟悉Hadoop的安装流程。 2、…

Mybatis plue(二) 核心功能

核心功能 P5 条件构造器 mybatisplus支持各种复杂的where条件,可以满足日常开发的所有需求 wrapper就是条件构造器,wrapper就是顶层的, 示例: 查询出名字带0,存款大于等于1000的人的id,username,info,balance字段 Testvoid te…

简单的安全密码生成器PwGen

什么是 PwGen ? PwGen 是一个简单的 Docker Web 应用程序,旨在生成具有可自定义选项的安全密码或密码短语。用户可以选择生成具有特定标准的随机密码或由随机单词组成的密码。其他功能包括在密码中包含大写字母、数字和特殊字符的选项,或者将…

如何在比特币上验证ZK Proofs

1. 引言 前序博客有: 基于BitVM的乐观 BTC bridgeBitVM:Bitcoin的链下合约Bitcoin Bridge:治愈还是诅咒?BitVM2:比特币上的无需许可验证以比特币脚本来实现SNARK VerifierClementine:Citrea的基于BitVM的…

C# serialPort

初始化SerialPort对象打开和关闭串行端口读取和写入数据事件处理注意事项 System.IO.Ports.SerialPort 类是C#中用于串行通信的类。它提供了一组属性和方法,用于配置串行端口、读取和写入数据,以及处理串行通信中的事件。 初始化SerialPort对象 首先&a…

【性能测试】接口测试各知识第1篇:接口测试,学习目标【附代码文档】

接口测试完整教程(附代码资料)主要内容讲述:接口测试,学习目标学习目标,2. 接口测试课程大纲,3. 接口学完样品,4. 学完课程,学到什么,5. 参考:,1. 理解接口的概念。学习目标,RESTFUL1. 理解接口的概念,2.什么是接口测试…

Day65-企业级防火墙iptables精讲1

Day65-企业级防火墙iptables精讲1 补充:1.什么是防火墙?2.防火墙种类2.1 商用防火墙介绍2.2 Linux下防火墙介绍 3.选择何种防火墙?4.企业级架构最佳防火墙场景5.学好iptables的技术栈基础6.Iptables是什么?7.Iptables企业常用场景…

C++的并发世界(三)——线程对象生命周期

0.案例代码 先看下面一个例子&#xff1a; #include <iostream> #include <thread>void ThreadMain() {std::cout << "begin sub thread:" << std::this_thread::get_id()<<std::endl;for (int i 0; i < 10; i){std::cout <&…

海豚调度任务类型Apache SeaTunnel部署指南

Apache DolphinScheduler已支持Apache SeaTunnel任务类型&#xff0c;本文介绍了SeaTunnel任务类型如何创建&#xff0c;任务参数&#xff0c;以及任务样例。 一、Apache SeaTunnel SeaTunnel 任务类型&#xff0c;用于创建并执行 SeaTunnel 类型任务。worker 执行该任务的时…

python项目练习——12.在线购物商城应用程序

项目功能分析&#xff1a; 这个项目可以让用户浏览商品、添加商品到购物车、进行结账等操作。这个项目涉及到数据库操作、用户认证、支付集成等方面的技术。 代码示例&#xff1a; # models.py from django.db import models from django.contrib.auth.models import User cl…

前端学习<四>JavaScript基础——01-编程语言和JavaScript简介

计算机语言 概念 计算机语言&#xff1a;人与计算机之间通信的语言。它是人与计算机之间传递信息的媒介&#xff0c;它通过特定的语法规则和语义约定&#xff0c;将人类可理解的指令转化为计算机可以执行的机器指令。 计算机程序&#xff1a;就是计算机所执行的一系列的指令…

下载kibana安装包 ubuntu 23 进行安装

要在 Ubuntu 23 上手动下载 Kibana 安装包并进行安装&#xff0c;您可以遵循以下步骤&#xff1a; 步骤 1&#xff1a;下载 Kibana 安装包 访问 Elastic 官方网站的 Kibana 下载页面。选择适合您系统架构&#xff08;通常是 amd64 对应于 x86_64 架构&#xff09;的 Kibana 版…

【js】监听文件上传下载进度,设置请求头信息与获取响应头信息

监听文件上传下载进度 例子&#xff1a;html部分 <input type"file" id"selectFile"> <span id"progress1"></span><button id"downloadFile">download</button> <span id"progress2"&g…

关联对象介绍

关联对象的作用 在分类里面&#xff0c;不可以直接为分类添加属性 在代理中&#xff0c;不可以直接为代理添加属性 在普通类中&#xff0c;property (assign, nonatomic) int age; 会做三件事&#xff1a; 生成age的成员变量生成age的get、set方法的声明生成age的get、set方…

使用 Docker 部署 Puter 云桌面系统

1&#xff09;Puter 介绍 :::info GitHub&#xff1a;https://github.com/HeyPuter/puter ::: Puter 是一个先进的开源桌面环境&#xff0c;运行在浏览器中&#xff0c;旨在具备丰富的功能、异常快速和高度可扩展性。它可以用于构建远程桌面环境&#xff0c;也可以作为云存储服…