this 到底指向谁

ObjectKaz Lv4

了解 this 的指向

函数外的 this

  1. 在浏览器中运行
1
console.log(this); // window

结果得到的是 window 对象。

  1. Node 下运行
1
console.log(this); // null

结果得到的是空对象。

但发现:

1
console.log(this === module.exports); //true

这个空对象实际上是模块的导出对象。

  1. Web Worker 下运行

新建一个文件,名为 worker.js 代码如下:

worker.js
1
console.log(this); // self

在另一个文件中引入:

worker.js
1
let worker = new Worker('./worker.js');

结果 this 指向 self 对象。

  1. ES6 模块运行:
1
2
3
<script type="module">
console.log(this);
</script>

得到的结果为 null

在这几种情况下,即使开启严格模式,得到的结果是不变的。

小结一下:

环境浏览器Node.jsWeb WorkerES6 Module
非严格模式windowmodule.exportsselfundefined
严格模式windowmodule.exportsselfundefined

函数中的 this

对于一个函数来说,它的 this 指向就比较复杂了。

  1. 先看一个最简单的例子:
1
2
3
4
5
function outputThis()
{
console.log(this);
}
outputThis()

这种情况下:[1]

环境浏览器Node.jsWeb WorkerES6 Module
非严格模式windowglobalselfundefined
严格模式undefinedundefinedundefinedundefined

对于非严格模式的情况,这些 this 有一个统一的名称 globalThis,用来表示全局作用域下的 this

  1. 如果把函数挂载到一个对象上:
1
2
3
4
5
6
7
8
9
10
11
12
function outputThis()
{
console.log(this);
}

let obj = {
id: 1,
name: '001',
outputThis
}

obj.outputThis(); //obj
  1. 对于对象中的属性和方法:
1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
id: 1,
name: '001',
nickName: this.name, // 这里的 this 仍指向对象外的 this
outputThis() {
console.log(this);
}
}

let outputThis = obj.outputThis;
obj.outputThis(); //obj
outputThis(); // globalThis

这种情况下:

  • 属性直接使用对象外的 this
  • 若方法挂载到了对象 obj内,那么 this 为所挂载的对象 obj
  • 若方法没有挂载到对象上,那么 this 则为 globalThis

如果对象中又嵌套了对象:

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
id: 1,
name: '001',
nickName: this.name, // 这里的 this 仍指向对象外的 this
foo: {
id: 2,
outputThis() {
console.log(this);
}
}
}
obj.foo.outputThis(); // foo

这种情况下,outputThis 挂载到了 obj.foo 对象上,this 的值为 foo 对象。

再看一个更复杂的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj = {
id: 1,
name: '001',
nickName: this.name, // 这里的 this 仍指向对象外的 this
outputThis() {
console.log(this);
}
}

function foo()
{
let outputThis = obj.outputThis
outputThis()
}

foo(); // globalThis

这种情况,将 outputThis 赋值给变量后,就直接调用,而没有挂载到哪个对象,结果便是 globalThis

总结一下 :

  • 对于函数调用,函数调用时挂载到了哪个对象上,this 就指向哪个对象,否则指向 globalThis
  • 函数中的 this 与函数所在作用域无关。

new 运算符与 this

当使用 new 调用函数时,this 指向一个新创建的对象。

1
2
3
4
5
6
7
function Foo()
{
this.id = 666;
console.log(this);
}

new Foo() // Foo

箭头函数中的 this

箭头函数没有自己的 this,它使用的是箭头函数外的 this

1
2
3
4
5
6
7
8
9
10
function foo()
{
let bar = () => console.log(this); // foo
bar();
}

let obj = {foo}

obj.foo();

小结

说简单点,谁调用函数,this 就指向谁。

说专业点,this 的指向,是在调用函数时根据执行上下文所动态确定的。[2]

修改 this 的指向

修改的方法

javascript 中, this 的指向过于灵活,甚至是可以修改的。

javascript 的函数对象提供了三种方法用于修改 this:

  • func.apply(thisArg, args)
  • func.call(thisArg, ...args)
  • func.bind(thisArg, ...args)

thisArg 是函数中的 this,而 args 则是函数的参数列表。

对于 applycall,修改 this后,它将立即调用这个函数:

1
2
3
4
5
6
7
function func(x,y)
{
console.log(this,x,y)
}

func.apply({a: 1},[1,2]) // {a:1} 1 2
func.call({a: 1},1,2) // {a:1} 1 2

对于 bind,则返回一个新的函数,新的函数调用时,bind 函数的 args 参数将和新函数的参数一起,作为函数的参数进行调用:

1
2
3
4
5
6
function func(x,y)
{
console.log(this,x,y)
}

func.bind({a: 1},1)(2) // {a:1} 1 2

对于箭头函数, thisArg 将不起任何作用,因为它没有自己的 this:

1
2
3
4
5
6

let func = (x,y) => {
console.log(this,x,y)
}

func.bind({a: 1},1)(2) // globalThis 1 2

如果 thisArg 传递的不是对象,那么它会在内部自动转换成对象:

1
2
3
4
5
6
7
8
function func()
{
console.log(this)
}

func.apply(2021) // Number(2021)
func.call(2021) // Number(2021)
func.bind(2021)() // Number(2021)

如果 thisArgnullundefined, 那么:

  • 使用 applycall 后的函数将使用 globalThis 代替原来的 thisArg[3][4]
  • 使用 bind 后的函数则取决于函数调用时所在的对象[5]
1
2
3
4
5
6
7
8
function func()
{
console.log(this)
}

func.apply(null) // globalThis
func.call(null) // globalThis
func.bind(null)() // globalThis

newbind 的优先级

如果我们把手动 bind 的函数使用 new 运算符、会发生什么?

1
2
3
4
5
6
7
8
9
function Foo()
{
this.id = 666;
console.log(this);
}

let Bar = Foo.bind({a: 1, b: 2})

new Bar() // Foo {id: 666}

结果,手动绑定的 this 便被丢弃了。

这意味着,new 运算符中绑定的 this 占据了更高的优先级.[5:1]

手动实现

前面说到了 bindcallapply 这三个函数可以修改 this 的指向,那么它们具体是怎么实现的呢?

不要忘了这个结论:对于函数调用,函数调用时挂载到了哪个对象上,this 就指向哪个对象,否则指向 globalThis

也就是说,如果我们想要修改 this,只需要把函数挂到对象上就可以了。

实现apply

下面我们来实现一个简单的 apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Function.prototype.myApply = function(thisArg, args)
{
// 对于空值,使用 globalThis 代替
if (thisArg === null || this.Arg === undefined){
thisArg = globalThis
}

// 对于非对象,将其包装成对象
if (typeof thisArg !== 'object' || typeof thisArg !== 'function') {
thisArg = new Object(thisArg)
}

// 判断参数是否为数组
if (!Array.isArray(args)) {
args = []
}

// 将函数挂载到 thisArg 上
thisArg.func = this
let ret = thisArg.func(...args)
delete thisArg.func
return ret
}

实现call

callapply 的实现是差不多的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.myCall = function(thisArg, ...args)
{
// 对于空值,使用 globalThis 代替
if (thisArg === null || this.Arg === undefined){
thisArg = globalThis
}

// 对于非对象,将其包装成对象
if (typeof thisArg !== 'object' || typeof thisArg !== 'function') {
thisArg = new Object(thisArg)
}

// 将函数挂载到 thisArg 上
thisArg.func = this
let ret = thisArg.func(...args)
delete thisArg.func
return ret
}

实现bind

对于 bind,它的实现可能稍微"复杂"一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myBind = function(thisArg, ...args)
{
let fn = this

// 这里不可以使用箭头函数,因为要用到函数内的 this
return function bindFn(...callArgs) {

// 如果 thisArg 为 null 那么使用原来的 this
// 如果 this 是通过 new 产生的,则绕过参数中的 this
if(thisArg === null || this.Arg === undefined || this instanceof bindFn){
thisArg = this;
}

return fn.apply(thisArg, [...args, ...callArgs])
}
}

  1. globalThis - MDN ↩︎

  2. 一网打尽 this, 对执行上下文说 yes - 知乎 ↩︎

  3. apply - MDN ↩︎

  4. call - MDN ↩︎

  5. bind - MDN ↩︎ ↩︎

  • 标题: this 到底指向谁
  • 作者: ObjectKaz
  • 创建于: 2021-04-26 04:01:11
  • 更新于: 2021-04-28 03:19:53
  • 链接: https://www.objectkaz.cn/3e703c16f149.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。