首页 > 编程语言 > 我所理解的JavaScript中的this指向
2020
10-10

我所理解的JavaScript中的this指向

前言

JS 中的 this 指向是一个经常被问到的问题,网上也有很多文章是关于 this 的。本文整理一下我理解下的 this 以及一些我比较疑惑的关于 this 问题。

this 指向

有几个 this 的指向问题是几乎每篇文章都会说的,比如作为函数直接调用,作为对象的方法调用, new 运算符执行中的 this 行为。比较通用的说法是, this 指向的是直接调用该函数的对象。其实也很好理解,就是为什么需要 this 这个关键字,就是我们有需要在函数内部对调用函数的对象进行操作的需求。但是有时候我们遇到的情况并不是像书上或 mdn 上遇到的典型的情况, this 的行为可能就会让我们感到有点疑惑。

函数的直接调用

当我们直接调用一个已经声明的函数,那么在非严格模式下,该函数内部的 this 指向的是全局对象,浏览器环境下就是 window 对象。

function f1(){
 return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window

//在Node中:
f1() === global;

当函数是在全局环境下定义的时候,这种现象是可以理解的,因为全局环境下定义的函数其实就是挂载在全局对象上的一个属性,比附上面的 f1 也可以理解为 window.f1。但我认为严格模式下的行为才是更符合 this 这个关键字的目的的,特别是我们的函数可能是在非全局环境(比如另一个函数中)定义和调用的,这种情况下 this 还指向 window 是不太合理的。所以在严格模式下,一个函数直接调用,它的 this 指向的是 undefined,如果我们想要得到非严格模式下的结果,那我们调用函数的方法就要改为 window.f1(),而如果函数是在非全局环境下定义的话,那么始终返回的是 undefined。我认为这样的行为是更符合逻辑的。

'use strict'
function d () {
 function e() {
  console.log(this)
 }
 console.log(this)
}

d()
//undefined
//undefined

window.d()
//Window{}
//undefined

这里在全局模式下使用 use strict 只是为了测试,实际使用还是尽量放在函数内局部使用严格模式,全局下的严格模式很容易导致出错。

函数作为对象的属性调用

这也是在代码中非常常见的场景,我认为这是比函数调用更好理解,也更能帮助我们理解 this 行为的场景。简单的来说就是 this 指向的是 直接 调用函数的那个对象。并且要注意的是,这跟函数在哪里定义的是无关的,我们看 this,看的就是从哪里调用的函数。

//在对象内部定义
var o = {
 prop: 37,
 f: function() {
 return this.prop;
 }
};

console.log(o.f()); // 37

//在对象外部定义
var o = {prop: 37};

function independent() {
 return this.prop;
}

o.f = independent;

console.log(o.f()); // 37

//在对象内部定义,但是给外部变量引用并执行
var o = {
 prop: 37,
 f: function() {
  console.log(this)
 return this.prop;
 }
};
var prop = 100;
var m = o.f;
console.log(m());
//Window{}
//100

上面的段落我给 直接 这两个字加粗了,想要表达的意思是当我们通过多个对象的属性嵌套找到并调用函数,那么最后那个最接近函数的对象就是函数 this 的指向。

var o = {
 a:10,
 b:{
  a:12,
  fn:function(){
   console.log(this.a); //12
  }
 }
}
o.b.fn();

var o = {
 a:10,
 b:{
  // a:12,
  fn:function(){
   console.log(this.a); //undefined
  }
 }
}
o.b.fn();

为什么我说这个场景能够帮助我们理解,原因就是它反映出 this 这个关键字的本质。JS 中的函数也是一种对象,在我们的执行环境中的活动对象保存的也只是函数对象的一个引用,如果这个引用是保存在活动对象中的某个对象的属性中(即我们通过活动对象中的某个对象的属性找到该函数),那么函数执行的时候 this 就会指向这个对象,这也是为什么多层对象的调用,还是最靠近函数的那个对象作为 this。虽然在代码中我们的函数是在对象中定义的,但是实际在内存中,对象中只保存着函数的引用,函数自己是在一个单独的内存空间中。所以我们通过哪个对象找到函数并执行,函数中的 this 就指向这个对象。上面的直接调用 this 返回 undefined 也是说得通的。

通过原型的调用

有时我们是通过原型来执行公用的函数,此时已然符合我们上面的逻辑,我们通过哪个实例 找到 函数,那么 this 就指向那个实例。

var o = {
 f: function() { 
 return this.a + this.b; 
 }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5

箭头函数

箭头函数并没有自己的 this,箭头函数中的 this是它所在的执行环境中的 thismdn 写的是封闭的词法环境),当你遇到箭头函数中的 this 不确定的时候,你可以想象把这个箭头函数换成 console.log(this),这个 console 的输出就是箭头函数中 this 的值,并且箭头函数的 this 是绑定的,不会改变(有时候看上去改变了是所在的 context 改变了)。还有一点需要注意的是,用 callapplybind 来调用箭头函数,第一个参数是没有意义的,也就是无法改变 this,如果仍需要使用,第一个参数应该传 null。看 mdn 给出的示例。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

// 接着上面的代码
// 作为对象的一个方法调用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// 尝试使用call来设定this
console.log(foo.call(obj) === globalObject); // true

// 尝试使用bind来设定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true

// 创建一个含有bar方法的obj对象,
// bar返回一个函数,
// 这个函数返回this,
// 这个返回的函数是以箭头函数创建的,
// 所以它的this被永久绑定到了它外层函数的this。
// bar的值可以在调用中设置,这反过来又设置了返回函数的值。
var obj = {
 bar: function() {
 var x = (() => this);
 return x;
 }
};

// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// 将返回的函数的引用赋值给fn。
var fn = obj.bar();

// 直接调用fn而不设置this,
// 通常(即不使用箭头函数的情况)默认为全局对象
// 若在严格模式则为undefined
console.log(fn() === obj); // true

// 但是注意,如果你只是引用obj的方法,
// 而没有调用它
var fn2 = obj.bar;
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window); // true

其他情况

还有一些情况我觉得比较简单,就一笔带过。
1. 当函数被用作事件处理函数时,它的 this 指向触发事件的元素。
2. 当代码被内联 on-event 处理函数调用时,它的this指向监听器所在的 DOM 元素,需要注意的是只有最外层的 this 是这样,如果里面还有嵌套函数,则嵌套函数的 this 在非严格模式下仍然指向全局对象。
3. 构造函数中的 this 请看之前的文章JavaScript中new操作符的解析和实现
4. bindcallapply 都一样,函数的 this 被绑定到第一个参数上。

总结

以上就是我所总结的 JS 中的 this 的一些要点,如果有什么遗漏或者错误的地方,欢迎指正。

到此这篇关于我所理解的JavaScript中的this指向的文章就介绍到这了,更多相关JavaScript this指向内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧