前言
this 是 js 的关键字之一,this 的值随着使用场景的变化而变化
全局环境(浏览器)
浏览器环境的顶层对象是 window,Node 是 global 对象。ES5认为全局环境和顶层对象是等价的,但是ES6开始之后顶层对象和全局环境属于两种环境。
var a = 1
const b = 2
let c = 3
this.d = 4
class E {}
console.log(this === window) // true
console.log(this.a, this.b, this.c, this.d, this.E) // 1 undefined undefined 4 undefined
function fun1() {
console.log(a, b, c, d, E) // 1 2 3 4 E {}
}
fun1()
classDiagram
window <|-- a
window <|-- d
env <|-- a
env <|-- b
env <|-- c
env <|-- d
env <|-- E
这里可以发现 let const 和 class 申明的变量都在全局环境而不在顶层对象中,这是因为 ES6 的规定。推测之后新增的申明变量大概率仅存在于全局环境。import 引入的变量也属于全局变量
在全局环境中 this 指向 顶层对象,即 window,仅在浏览器的情况下
console.log(this === window) // true
函数
在函数的内部,this 的值取决于函数被调用的位置
正常函数的调用
function fun1 () {
console.log(this === window)
function fun3 () {
console.log(this === window)
}
fun3()
}
function fun2() {
fun1()
}
fun1() // true true
fun2() // true true
classDiagram
window <|.. fun1
window <|-- fun2
fun1 <|-- fun3
fun2 <|.. fun1
主动调用 fun1 和 fun2 的环境是全局环境,且这是在非严格环境下。
对象内的函数
对象内的函数的 this 指向调用者。
const obj = {
a: {
a: function () {
console.log(this === obj.a)
}
},
b: function () {
console.log(this === obj)
}
}
obj.a.a() // true
obj.b() // true
classDiagram
obj_a <|-- obj_a_a_this
obj <|-- obj_b_this
obj.a.a() 的调用者是 obj.a
class P {
constructor() {
console.log(this)
}
a = function () {
return this
}
}
const p = new P() // p
console.log(p.a() === p) // true
函数内的函数的 this
function fun1 () {
function fun2() {
console.log(this)
}
fun2()
}
fun1() // window
fun1.bind({})() // window
classDiagram
window <|-- fun1
obj <|.. fun1_this
fun1 <|-- fun2_this
箭头函数 () => {}
ES6 添加的箭头函数。
箭头函数是没有自己的 this 对象,不同于正常函数 this 的动态,箭头函数是静态的,固定的指向函数上层的 this
对象内的箭头函数
const obj1 = {
a: function () {
console.log(this === obj1)
},
b: () => {
console.log(this === window)
}
}
obj1.a() // true
obj1.b() // true
classDiagram
obj1 <|-- a_this
window <|-- this
this <|-- obj1_b_this
在对象内的常规函数被调用,this 指向调用的对象,然后如果是 obj.a.a() 这种的情况指向会是 obj.a
对象内的箭头函数的 this 指向会是这个对象所在作用域的 this。可以看下面这个例子
function fun1 () {
const obj = {
a: () => {
console.log(this)
}
}
obj.a()
}
fun1() // window
fun1.bind({})() // {}
classDiagram
window <|-- fun1
window <|.. fun1_this: 正常调用
obj <|.. fun1_this: bind
fun1_this <|-- obj_a_this
这里将 fun1 函数的 this 指向 {}。当我们访问箭头函数的时候就会找到 obj 所在作用域的 this 指向,即 {}
函数内的箭头函数
function fun1() {
const fun2 = () => {
console.log(this)
}
fun2()
}
fun1() // window
fun1.bind({})() // {}
classDiagram
window <|-- fun1
obj <|-- fun1_this
fun1_this <|-- fun2_this
函数内的箭头函数 this 指向 fun1 函数的 this。正常情况 fun1 的 this 指向 window,当然可以被 bind() 改变 this 指向
bind apply call 调用箭头函数
const fun1 = () => {
console.log(this)
}
fun1()
fun1.bind({}) // window
fun1.apply({}) // window
fun1.call({}) // window
对箭头函数使用改变 this 指向的方法是没有用的
常规函数 & 箭头函数 & bind & 严格模式 结合使用
非严格模式下调用
function fun1() {
console.log('fun1', this)
function fun2() {
console.log('fun2', this)
function fun3() {
console.log('fun3', this)
}
fun3()
const fun4 = () => {
console.log('fun4', this)
}
fun4()
}
fun2()
const fun5 = () => {
console.log('fun5', this)
function fun6() {
console.log('fun6', this)
}
fun6()
}
fun5()
}
fun1.bind({})()
打印结果
fun1 {}
fun2 window
fun3 window
fun4 window
fun5 {}
fun6 window
fun1:fun1中的this被bind指定成{}fun2:fun2中的this指向fun1,fun1指向windowfun3:fun3中的this指向fun2,fun2指向fun1,fun1指向windowfun4:fun4中的this指向fun2中的this,fun2的this指向fun1,fun1指向windowfun5:fun5中的this指向fun1中的this,fun1中的this被bind指定成{}fun6:fun6中的this指向fun5,fun5指向fun1,fun1指向window
严格模式下这些的 window 都会变成 undefined。因为严格模式下 fun1 指向了 undefined
classDiagram
window <|.. fun1: 非严格模式
window <|-- this
undefined <|.. fun1: 严格模式
fun1 <|-- fun2
fun1 <|-- fun2_this
fun1 <|-- fun5
obj <|-- fun1_this
fun1_this <|-- fun5_this
fun2 <|-- fun3_this
fun2 <|-- fun4_this
fun5 <|-- fun6_this
闭包
let obj1 = {
a: function () {
const that = this
console.log(this === obj1)
return function () {
console.log(this === window)
console.log(that === obj1)
}
},
b: function () {
return () => {
console.log(this === obj1)
}
},
c: () => {
return () => {
console.log(this === window)
}
}
}
obj1.a()() // true true true
obj1.b()() // true
obj1.c()() // true
闭包即匿名函数不会绑定到对象上,所以还是指向 window 对象。
上面的 obj1.a() 把函数内的 this 绑定到变量上,闭包就能直接使用了,同样也可以用箭头函数。这是在 this 对象变化的时候常用的处理方式
常规函数 & 箭头函数 & 对象 & 严格模式 & 闭包
const obj = {
a: function () {
return function () {
console.log(this)
}
},
b: function () {
return () => {
console.log(this)
}
},
c: () => {
return function () {
console.log(this)
}
},
d: () => {
return () => {
console.log(this)
}
}
}
obj.a()() // window
obj.b()() // obj
obj.c()() // window
obj.d()() // window
classDiagram
window <|.. obj_a
undefined <|.. obj_a
obj_a <|-- obj_a_f_this
obj <|-- obj_b_this
obj_b_this <|-- obj_b_f_this
window <|.. obj_c
undefined <|.. obj_c
obj_c <|-- obj_c_f_this
window <|-- this
this <|-- obj_d_this
obj_d_this <|-- obj_d_f_this
严格模式
'use strict'
function fun1 () {
console.log(this)
}
fun1() // undefined
const fun2 = () => {
console.log(this === window)
}
fun2() // true
;(function () {
console.log(this) // undefined
})()
之前的情况都不是严格模式,在严格模式之下函数内的 this 的值为 undefined。
这里的常规函数和闭包都是 undefined,但是箭头函数却没有,因为箭头函数指向的是上层的 this,就是 window。
HTML 事件
const btn = document.getElementById('btn')
btn.addEventListener('click', () => {
console.log(this === window) // true
})
btn.addEventListener('click', function () {
console.log(this === btn) // true
})
在 HTML 事件的回调函数中 this 指向的是触发事件的元素,我们可以用箭头函数修改指向
globalThis 对象
固定的顶层对象
总结
-
this的值是一种当调用当前环境的引用,即谁调用this所在的环境那么this就指向它。全局环境可以理解成一个函数 -
严格模式规则需要熟悉,以及哪些情况会触发严格模式。例如
babel的打包 -
this单独使用的情况比较好理解,当多个情况结合使用的时候就考验我们基础扎不扎实了