这个 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)
标签:function,obj,函数,指向,对象,JS,var,fn
From: https://www.cnblogs.com/chenjieyouge/p/18117732