JS 上下文 this 指向总结
这个 js 语言中的 this
和其他面向对象的语言有本质的不同, 也更复杂, 它更多取决于函数在不同场景下的调用方式, 要理解它并总结出它的规律的话, 优先要从上下文
这个概念认知说起.
理解上下文
上下文 context
可理解为程序执行时的背景环境, 包含了在特定时刻程序所需要的所有信息. 包括变量的值, 函数的调用情况, 执行的位置等.
上下文的核心应用场景在于, 程序状态管理, 函数调用, 内存管理, 异常处理等. 本篇这里是以 JS 编程语言层面的上下文 this
指向总结, 就更多是一种规则梳理.
- 函数中可以使用
this
关键字, 它表示函数的上下文 - 与中文中的
这
类似, 函数中this
指向必须通过调用函数时
的 "前言后语" 来判断 - 如果函数不调用, 则不能确定函数的上下文
规则01: 对象.方法(), this 指向该打点的对象
当出现 对象.方法()
的场景时, 方法里面的 this 就是这个对象.
// case 01
function fn() {
console.log(this.a + this.b);
}
var obj = {
a: 100,
b: 200,
fn: fn
}
obj.fn() // this 指向 obj
这里便是构成了 obj.fn()
的形式, 此时对象的方法 fn
中的 this 则指向该对象 obj
则最后输出 300.
// case 02
var obj1 = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
}
var obj2 = {
a: 3,
b: 4,
fn: obj1.fn
}
obj2.fn() // this 指向 obj2
首先要注意对于 obj1
来说, 里面的 this
在方法没有被调用的时候, 是不知道具体指向的.
然后分析 obj2.fn()
是符合对象.方法()
1的, 虽然这里的 fn
是 obj1 的 fn
但 this
仍指向 obj1
则最后输出 7.
// case 03
function outer() {
// 这里的 a, b 是内部变量, 其实是个干扰项
var a = 1
var b = 2
// 外层函数返回一个对象
return {
a: 3,
b: 4,
fn: function () {
console.log(this.a + this.b);
}
}
}
outer().fn() // this 指向 outer() 返回的对象
分析调用可知 outer()
返回的是一个对象, 对象再调用其方法 fn
所以还是适用于 对象.方法()
的形式, 因此这里的 this
便是指向其返回的对象, 则输出 7.
// case 04
function fn() {
console.log(this.a + this.b);
}
var obj = {
a: 1,
b: 2,
c: [{
a: 3,
b: 4,
c: fn
}]
}
var a = 5 // a 是全局变量
obj.c[0].c() // this 指向 c 里面的对象
分析调用可知, obj.c[0]
是一个对象, 然后再调用里面的 fn
方法, 则还是构成了 对象.方法()
形式, 则里面的 this
指向 c 里面的对象, 这个全局的 5
没有啥关系, 则最后输出 7.
规则02: 圆括号直接调用函数(), this 指向 window 对象
当出现普通 函数()
的场景时, 函数里面的 this 在浏览器中指向 window对象
在 nodejs
中则指向空对象 {}
本篇的所有分析均用浏览器哈, 不在 nodejs 中运行.
// case 01
var obj = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
}
var a = 3
var b = 4
var fn = obj.fn
fn()
从调用分析, 这里 fn
的调用首先是进行了一个函数的提取 obj.fn
, 然后再调用则形成了 函数()
的形式, 则此时 this
在浏览器指向了 window 对象
, 全局变量 a, b 都是其属性, 则最后输出7.
注意在 nodejs 里面上面的代码是不能运行的, 因为其没有 window 对象哦
// case 02
function fn() {
return this.a + this.b
}
// 全局变量
var a = 1
var b = 2
var obj = {
a: 3,
b: fn(), // 函数调用()
fn: fn
}
var result = obj.fn() // 对象.方法()
console.log(result);
先执行 obj
的定义, 里面的 b
直接调用了函数 fn
其 this
指向 window
全局对象, 此时 b
的值为 1 + 2 = 3
然后从调用分析, obj.fn()
的形式是 对象.方法()
其 this
指向 obj
// 此时的 obj
obj = {
a: 3,
b: 6,
fn: fn
}
最后形成了 对象.方法
形式, 则最后输出6.
// case 03
var c = 1
var obj = {
a: function () {
var c = 3
return this.b
},
b: function () {
var c = 4
document.write(this.c)
},
c: 2
}
var obj1 = obj.a()
obj1()
从调用分析, obj.a()
形式为 对象.方法()
则 this
指向 obj
对象
则此时 a
方法里面的 return this.b
的值为 obj
的 b
是个方法.
再进行调用 obj1
则形如 函数()
则 this
指向了全局 window
则此时的 c
是1, 而非函数里面的变量 4,
因此最后输出为1.
规则03: 类数组对象 数组[下标] (), this 指向该类数组
当类数组里面的元素是 function
时,里面的 this
指向该类数组.
// case 01
var arr = ['a', 'b', 'c', function () {
console.log(this[0]);
}]
arr[3]()
从调用分析, arr[3] ()
满足形如 数组[下标] ()
的形式, 则 this
指向该数组 arr
, 最终输出 'a`.
对于类数组对象, 即所有键名为自然数序列 (从 0 开始), 且有 length
的对象, 最常见的是 arguments
.
// case 02
function fn() {
arguments[3]()
}
fn('a', 'b', 'c', function () {
console.log(this[1]);
})
从调用分析, arguments[3] ()
满足形如 数组[下标] ()
的形式, 则 this
指向 arguments
即 fn 在调用时传递的实参数组, 下标1则输出 'b'.
// case 03
var a = 6
var obj = {
a: 5,
b: 2,
c: [ 1, a, function () { document.write(this[1])} ]
}
obj.c[2]()
从调用分析, obj.c
是一个数组, 然后再进行下标调用, 即形如 数组[下标] ()
的形式, this
指向数组本身.
下标为1 则指向了数组里面的 a
, 这里指向了全局变量 a
, 则最后输出了 6.
规则04: IIFE中的函数, this 指向 window 对象
IIFE
表示立即执行函数, 定义后立即调用. 这在项目中经常用于在页面加载完后, 立即执行获取后端接收的数据方法, 定义 + 调用 的方式来渲染页面.
// 写法1: 将函数用 () 包裹起来再调用 ()
(function () {
console.log(123)
})();
// 写法2: 函数前面加 void, 最后再调用
void function fn() {
console.log(123)
}()
// case 01
var a = 1
var obj = {
a: 2,
fn: (function () {
var a = this.a
return function () {
console.log(a + this.a);
}
})() // IIFE, this 指向 window
}
obj.fn() // 对象.方法 this 指向 obj
从调用分析,
obj.fn
是一个立即执行函数, 会先执行, 此时的 this
指向全局 window
, 则闭包里面的 this.a
为外面的 1.
obj.fn()
形如 对象.方法()
,此 this
指向 obj
, 则 fn
返回的函数里面的 this.a
的值为 obj.a
的值为 2,
因此最后输出了3.
规则05: 用定时器, 延时器调用函数, this 指向 window 对象
通常在做一些异步任务
如想后端请求数据啥的, 就容易改变 this
指向, 通常的操作是可以用一个别的变量如叫 that
或者 self
来先指向 this
以保证 this
的指向不会改变. 当然这些前提是, 咱们能识别问题.
- setInterval (函数, 时间)
- setTimeout(函数, 时间)
// case 01
var obj = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
}
var a = 3
var b = 4
setTimeout(obj.fn, 2000)
从调用分析, obj.fn
是一个函数, 然后外面被延时器调用, 2秒后执行, 则此时的 this
指向全局 window
则最后输出为7.
这里可以进行一个调用变化.
// case 02
var obj = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
}
var a = 3
var b = 4
setTimeout(function () {
obj.fn() // 这里真正调用
}, 2000);
从调用分析, 这里的 setTimeout
并不是调用了函数, 只是将整体延迟了 2秒.
而真正调用函数的是 obj.fn()
形如 对象.方法
, 则 this
指向的是 obj
, 则最后输出的是 3.
规则06: 事件处理函数, this 指向绑定事件的 DOM
Dom元素.onclick = function () { }
比如要实现一个效果, 当我们点击哪个盒子, 哪个盒子就变红, 要求使用同一个事件函数处理实现, 但不能用事件委托的方式.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
float: left;
margin-right: 10px;
width: 200px;
height: 200px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="box1">box1</div>
<div id="box2">box2</div>
<div id="box3">box3</div>
<script>
function setColorRed() {
// 这里的 this 指向前面绑定的 DOM
this.style.backgroundColor = 'red'
}
var box1 = document.getElementById('box1')
var box2 = document.getElementById('box2')
var box3 = document.getElementById('box3')
box1.onclick = setColorRed
box2.onclick = setColorRed
box3.onclick = setColorRed
</script>
</body>
</html>
注意这里的 this
是指向当前绑定的元素, 而 e.target
指的是内层触发的元素, 这俩不一样哦.
再对上面的案例做一个升级: 点击哪个盒子, 哪个盒子在 2秒 后就变红, 也是要求用一个事件函数来实现哦.
这咋一看似乎蛮简单:
function setColorRed() {
// 直接放延迟函数是不行的,
// 因为它的 this 指向从当前 dom 变成了 window
setTimeout(function () {
this.style.backgroundColor = 'red'
})
}
这样其实是不行的, 因为又之前的规则5所知, 在延时器调用函数时, 里面的 this
指向的是 window
对象.
这里最常用的一个巧妙办法是:
- 先用一个变量比如叫
self
来保存原来的this
指向的是 Dom, 进行备份 - 然后在延时器中, 用
self
来替代this
即可, 这样还是满足规则6的
function setColorRed() {
// 这里的 this 指向当前 dom
var self = this
setTimeout(function () {
// 用 self 替换 this, 因为这里的 this 会指向 window
self.style.backgroundColor = 'red'
}, 2000)
}
函数的 call 和 apply 方法能指定 this
在 js 中数组和函数都是对象 object
, 既然是对象, 那就会有一些原型上的方法, 这里的 call / apply
作为函数对象的方法, 其功能是能指定函数的上下文 this
比如要统计语数英成绩, 对每个小朋友, 比如油哥:
var youge = {
chinese: 80,
math: 70,
english: 60
}
有一个统计成绩的函数 sum
function sum() {
console.log(this.chinese + this.math + this.english);
}
这两个怎么进行关联呢, 简单的方式将 sum
写进 youge
对象, 然后构造出 对象.方法()
的方式, 则 this
指向该对象.
var youge = {
chinese: 80,
math: 70,
english: 60,
sum: function () {
console.log(this.chinese + this.math + this.english);
}
}
youge.sum() // 对象.方法() this 指向 对象, 输出210
但 js 提供了更简单的方法, 即可通过 call
或者 apply
方法直接指定函数对象的 this
function sum() {
console.log(this.chinese + this.math + this.english);
}
var youge = {
chinese: 80,
math: 70,
english: 60
}
sum.call(youge)
sum.apply(youge)
这俩的主要区别在于当函数有额外的参数时候:
sum.call(youge, 1, 2, 3)
额外的参数是逗号展开的sum.apply(youge, [1, 2, 3)
额外的参数是拼装成数组的
比如上例还有额外的竞赛甲方需要补充, 则便可用额外的参数啦.
function sum(a, b) {
console.log(this.chinese + this.math + this.english + a + b);
}
var youge = {
chinese: 80,
math: 70,
english: 60
}
// 额外的两项加分
sum.call(youge, 30, 10)
sum.apply(youge, [30, 10])
如果额外参数传递正确的话, 那都会输出总分是 250 啦.
上下文规则总结
规则 | 上下文 this指向 |
---|---|
对象.方法() | 对象 |
函数() | window |
数组[下标] () | 数组 |
IIFE | window |
定时器 / 延时器 | window |
Dom事件处理函数 | 绑定 Dom 的元素 |
call / apply | 任意指定 |
用 new 调用函数 | 秘密创建出的对象 |
最后一条是关于构造函数的, 这里就不展开了, 记住 this
指向是 new
出来的对象就行啦.
最关键的一条是要去动态分析函数的调用过程来确定上下文, 而非看定义哦.
JS 上下文 this 指向总结的更多相关文章
- JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解
前 言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...
- JS中this指向的更改
JS中this指向的更改 JavaScript 中 this 的指向问题 前面已经总结过,但在实际开中, 很多场景都需要改变 this 的指向. 现在我们讨论更改 this 指向的问题. call更改 ...
- 关于js中this指向的总结
js中this指向问题一直是个坑,之前一直是懵懵懂懂的,大概知道一点,但一直不知道各种情况下指向有什么区别,今天亲自动手测试了下this的指向. 1.在对象中的this对象中的this指向我们创建的对 ...
- 关于js中this指向的理解总结!
关于js中this指向的理解! this是什么?定义:this是包含它的函数作为方法被调用时所属的对象. 首先,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁 ...
- Atitit.js this错误指向window的解决方案
Atitit.js this错误指向window的解决方案 1.1. 出现地点and解决之道1 1.2. call,apply和bind这三个方法2 1.2.1. Function.prototype ...
- 前端js中this指向及改变this指向的方法
js中this指向是一个难点,花了很长时间来整理和学习相关的知识点. 一. this this是JS中的关键字, 它始终指向了一个对象, this是一个指针; 参考博文: JavaScript函数中的 ...
- js中this指向的三种情况
js中this指向的几种情况一.全局作用域或者普通函数自执行中this指向全局对象window,普通函数的自执行会进行预编译,然后预编译this的指向是window //全局作用域 console.l ...
- JavaScript , js 上下文(this 的指代)
上下文 代表 this 变量的值, 以及 它的 指代; 它决定一个函数怎么被调用; 当一个函数作为一个对象的方法被调用的时候, this总是指向 调用这个方法的对象. ----- 1 ,情况一: 字面 ...
- 面试官问:JS的this指向
前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...
- 关于js中this指向的问题
this的绑定规则有4种 默认绑定 隐性绑定 显性绑定 new绑定 this绑定优先级 new 绑定 > 显性绑定 > 隐性绑定 > 默认绑定 1.如果函数被new 修饰 this绑 ...
随机推荐
- 嵌入式linux下的FTP服务器配置记录
嵌入式linux FTP服务器 一般嵌入式Linux下的FTP服务器会有什么要求呢?一般来说差不多如下所示 账号认证,需要特定用户才能访问(不一定要和登录用户挂钩) 根目录固定在一个固定的位置,且不能 ...
- “未能加载工具箱项xxx,将从工具箱中将其删除”提示出现原因及解决方案
https://www.thinbug.com/q/27289366 https://social.msdn.microsoft.com/Forums/vstudio/en-US/77e10b58-4 ...
- C# 将list进行随机排序
private List<T> RandomSortList<T>(List<T> ListT) { Random random = new Random(); L ...
- 异步编程——CompletableFuture详解
Future JDK5 新增了Future接口,用于描述一个异步计算的结果. 虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,我们必须使用Future.g ...
- 【记录】C/C++-关于I/O的坑与教训
吐槽 每每读取字符串时,倘若稍有灵活的操作,总会遇上诡异奇怪的事情.究其原因,就是没完全理解一些基本读写函数的机制.这次做Uva227就把I/O上的问题全暴露出来了.想来还是应该记录一些经验教训. 记 ...
- Kubernetes 编译 kubeadm 修改证书有效期到 100 年
前言 kubeadm 生成的客户端证书在 1 年后到期.过期后,会导致服务不可用,使用过程中会出现:x509: certificate has expired or is not yet valid. ...
- NumPy学习7
今天学习了: 13, NumPy字符串处理函数 14, NumPy数学函数 15, NumPy算术运算 numpy_test7.py : import numpy as np ''' 13, NumP ...
- 密码编码学与网络安全 原理与实践(第七版)William Stallings---读书笔记(1.1-1.5)
密码编码学与网络安全 原理与实践(第七版)William Stallings---读书笔记 第一部分 概览 第1章 计算机与网络安全概念 密码算法与协议又可分为4个主要领域: 对称加密 加密任意大小的 ...
- 【Web】Servlet基本概念
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据, ...
- 【Java】内部类详解
说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就来一探究竟. 一.内 ...