这个 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. 并发编程 - 线程同步(九)之信号量Semaphore

    前面对自旋锁SpinLock进行了详细学习,今天我们将学习另一个种同步机制--信号量Semaphore. 01.信号量是什么? 在 C# 中,信号量(Semaphore)是一种用于线程同步的机制,能够 ...

  2. 【Unity】投影矩阵和线性深度推导

    [Unity]投影矩阵和线性深度推导 网络上有很多投影矩阵的推导,也有很多声称是基于 Unity 的,但和我的实测都不一致(现在看来是因为这些文章并不全面),此外有一些 Unity 本身的函数我也搞不 ...

  3. Linux - 禁ping & 开放访问端口

    适用于Linux操作系统 禁ping 1.编辑 /etc/sysctl.conf 文件,文件末尾增加如下内容后,保存退出. net.ipv4.icmp_echo_ignore_all = 1 2.在命 ...

  4. 面试题53 - II. 0~n-1中缺失的数字

    地址:https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/ <?php /** 一个长度为n-1的递增排序数组中的所有数字都是唯一的, ...

  5. sql server 2017 STRING_AGG() 替代方案

    SELECT @StuId='"'+STRING_AGG(Id,'","')+'"'FROM( SELECT 'a'+cast(Id as varchar) I ...

  6. CRYPTO-DSA

    CRYPTO-DSA 参考某位大佬的博客和nss的一些题目,这两天的DSA题目 DSA数字签名 | DexterJie'Blog [NCTF 2021]dsa task.py from Crypto. ...

  7. Supac 如何修改地址界限高层点

    编辑->图层->运算 2.选择z ->填写高度 如-180阶段 3.保存

  8. How to use the shell, terminal and the advanced tools

    How to use the shell, terminal and the advanced tools Introduction ‍ Why use English instead of Chin ...

  9. Python数据格式转换神器-提高办公效率

    一.引言 在工作日常里,数据转换总是让人头疼?别急,今天揭秘一个超级实用的Python技巧,帮你轻松搞定各种数据格式转换,提升工作效率不是梦! 场景1:你手头有一堆CSV格式的(逗号分隔符)数据,其他 ...

  10. study Rust-4【所有权】这个太重要了!

    由于Rust内存垃圾自动回收,那就得搞清楚这个所有权玩意.这个太重要了.因为关系到贯穿于你以后的程序编写. 几个概念: 一.移动 1.咱们一般语言,自己申请内存,自己管理和释放.就是new和free. ...