这个 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的, 虽然这里的 fnobj1 的 fnthis 仍指向 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 直接调用了函数 fnthis 指向 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 的值为 objb 是个方法.

再进行调用 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 指向总结的更多相关文章

  1. JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解

      前  言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...

  2. JS中this指向的更改

    JS中this指向的更改 JavaScript 中 this 的指向问题 前面已经总结过,但在实际开中, 很多场景都需要改变 this 的指向. 现在我们讨论更改 this 指向的问题. call更改 ...

  3. 关于js中this指向的总结

    js中this指向问题一直是个坑,之前一直是懵懵懂懂的,大概知道一点,但一直不知道各种情况下指向有什么区别,今天亲自动手测试了下this的指向. 1.在对象中的this对象中的this指向我们创建的对 ...

  4. 关于js中this指向的理解总结!

    关于js中this指向的理解! this是什么?定义:this是包含它的函数作为方法被调用时所属的对象. 首先,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁 ...

  5. Atitit.js this错误指向window的解决方案

    Atitit.js this错误指向window的解决方案 1.1. 出现地点and解决之道1 1.2. call,apply和bind这三个方法2 1.2.1. Function.prototype ...

  6. 前端js中this指向及改变this指向的方法

    js中this指向是一个难点,花了很长时间来整理和学习相关的知识点. 一. this this是JS中的关键字, 它始终指向了一个对象, this是一个指针; 参考博文: JavaScript函数中的 ...

  7. js中this指向的三种情况

    js中this指向的几种情况一.全局作用域或者普通函数自执行中this指向全局对象window,普通函数的自执行会进行预编译,然后预编译this的指向是window //全局作用域 console.l ...

  8. JavaScript , js 上下文(this 的指代)

    上下文 代表 this 变量的值, 以及 它的 指代; 它决定一个函数怎么被调用; 当一个函数作为一个对象的方法被调用的时候, this总是指向 调用这个方法的对象. ----- 1 ,情况一: 字面 ...

  9. 面试官问:JS的this指向

    前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...

  10. 关于js中this指向的问题

    this的绑定规则有4种 默认绑定 隐性绑定 显性绑定 new绑定 this绑定优先级 new 绑定 > 显性绑定 > 隐性绑定 > 默认绑定 1.如果函数被new 修饰 this绑 ...

随机推荐

  1. 嵌入式linux下的FTP服务器配置记录

    嵌入式linux FTP服务器 一般嵌入式Linux下的FTP服务器会有什么要求呢?一般来说差不多如下所示 账号认证,需要特定用户才能访问(不一定要和登录用户挂钩) 根目录固定在一个固定的位置,且不能 ...

  2. “未能加载工具箱项xxx,将从工具箱中将其删除”提示出现原因及解决方案

    https://www.thinbug.com/q/27289366 https://social.msdn.microsoft.com/Forums/vstudio/en-US/77e10b58-4 ...

  3. C# 将list进行随机排序

    private List<T> RandomSortList<T>(List<T> ListT) { Random random = new Random(); L ...

  4. 异步编程——CompletableFuture详解

    Future JDK5 新增了Future接口,用于描述一个异步计算的结果. 虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,我们必须使用Future.g ...

  5. 【记录】C/C++-关于I/O的坑与教训

    吐槽 每每读取字符串时,倘若稍有灵活的操作,总会遇上诡异奇怪的事情.究其原因,就是没完全理解一些基本读写函数的机制.这次做Uva227就把I/O上的问题全暴露出来了.想来还是应该记录一些经验教训. 记 ...

  6. Kubernetes 编译 kubeadm 修改证书有效期到 100 年

    前言 kubeadm 生成的客户端证书在 1 年后到期.过期后,会导致服务不可用,使用过程中会出现:x509: certificate has expired or is not yet valid. ...

  7. NumPy学习7

    今天学习了: 13, NumPy字符串处理函数 14, NumPy数学函数 15, NumPy算术运算 numpy_test7.py : import numpy as np ''' 13, NumP ...

  8. 密码编码学与网络安全 原理与实践(第七版)William Stallings---读书笔记(1.1-1.5)

    密码编码学与网络安全 原理与实践(第七版)William Stallings---读书笔记 第一部分 概览 第1章 计算机与网络安全概念 密码算法与协议又可分为4个主要领域: 对称加密 加密任意大小的 ...

  9. 【Web】Servlet基本概念

    Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据, ...

  10. 【Java】内部类详解

    说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就来一探究竟. 一.内 ...