Javascript基础系列之this指向

134 阅读7分钟

JS中this指向问题

写这篇文章的原因是因为在JS中this也是一个比较难捕捉的一个知识点,并且在面试中会出现在笔试题或者机试题中。为了加深印象,重新整理作为记录。

正文开始...

开始之前大家带着问题来看,跟着文章思路一起思考:

  1. 什么时候产生的this
  2. 箭头函数中的this(ES6+)
  3. 改变this指向的API有那些

this是什么

  • 全局this

首先我们新建一个test.html文件,然后在body中加入script标签,输出 console.log(this, Object.getPrototypeOf(this));

image.png

【浏览器执行环境下】打开浏览器的控制台,我们看到输出的都是 Window 对象,那么在浏览器环境下,全局this是一个window对象 image.png 【node环境下执行】当我们打开编辑器,执行 node test.js

image.pngnode环境下,全局的this居然是一个{}对象

  • 严格模式下函数内部 this(在js文件顶部添加'use strict'使用严格模式)

image.png

image.png严格模式下函数内部会是undefined,并且访问test会直接报错.

那么为什么use strict严格模式下全局this无法访问。 原因:

  • 未提前申明的变量不能使用,会报错
  • 不能用delete删除对象的属性
  • 定义的变量名不能重复申明
  • 函数内部的this不再指向全局对象

通常会看到this的指向并不都是指向全局对象,而是动态变化的。

image.png 以上例子可得出:

  1. 非严格模式普通函数this指向 => 在普通函数内部this指向的是window对象 2.构造函数的this指向 => 构造函数内部的this居然就是实例化的那个对象obj 3.对象定义的内部函数 => 肯定publicName肯定是放弃懒惰,内部的this也是指向obj
  • 接下来我们看下箭头函数

image.png

箭头函数不是没有自己的this吗,而且这里是obj2.getName()这不是一个隐式调用吗?应也是obj2这个对象才对,但是并不是,当改成箭头函数后,内部的this居然变成了全局的window对象了 我们看下babel对上面一段代码编译成es5的代码

image.png 左边为 es6代码,输出后为右边代码。

其实箭头函数是非常迷惑人的,而且外面是一个被调用的是一个对象,所以时常会给人一种幻觉,我们常听到一句this指向的是被调用的那个对象,那么这里箭头函数this指向的是window,而const定义的变量会被转换成var

那怎么能让getName指向的是本身自己的obj2

image.png

image.png 当把箭头函数改成普通函数,这个普通函数内部的this就指向obj3自己了

另一种情况:

image.png

image.png 此时你会发现打印的是放弃懒惰 outer

此时会发现this指向的是window,也就是说指向的那个被调用者,那被调用者是谁?

上面那段代码同等于下面,你仔细看

image.png 所以你现在是不是很清晰明白this指向的也是被调用的那个对象window了。 上一篇文章闭包已经介绍过作用域,这里不在赘述,可查阅Javascript基础系列之闭包

**注意:**必须在非严格模式下,此时的this才会指向window。 总结下: 非严格模式下

  • 普通函数内部的this指向的是window对象
  • 构造函数内的this指向的是实例化的那个对象
  • 普通申明的对象,如果调用的方法是箭头函数,那么内部this指向的是全局对象,如果不是那么指向的是被调用本身的那个对象

接下来我们看下面试题,加深理解:

image.png

image.png obj.b()的调用实际上在之前例子已经有讲,b方法是一个普通方法,内部this指向的就是被调用的obj对象,所以此时内部访问的a属性就是对象obj

var objb = obj.b,当我们看到这样的代码时,其实这段代码可以拆分以下

image.png 本质上就是将对象obj的一个方法b赋值给了window.objb的一个属性

所以objb()的调用也是window.objb()objb方法内部this自然指向的就是window对象,而我们用var a = 2这个默认会绑定在window对象上

obj.c(),因为c是一个箭头函数,所以内部的this就是指向的全局对象

obj.b.call(null)这个null是非常迷惑人,通常来说call不是改变函数内部this的指向吗,但是这里,如果call(null)实际上会默认指向window对象

objc.b()这打印的是3,其实与objb的赋值有异曲同工之笔

image.png 本质上就在objc动态的新增了一个属性b,而这个属性b赋值了一个方法,也就是下面这样

image.png 如果是const t = objc.b,至此你会发现,当我们执行t()时,此时打印的却是2那是因为const t定义的变量会编译成var从而t变量变成一个全局的window对象下的属性,本质上等价下面

image.png

  • 多层对象嵌套下的this

image.png 以上的结果是3,实际上我们从之前案例中明白,非严格模式下this指向被调用那个对象

所以你可以把上面那段代码看成下面这样

image.png

改变this指向

改变this指向方法: call\apply、bind,先介绍使用

  • call

image.png 正因为在计算属性中用了call所以在config.js中才能访问外部methods的方法,当我们为了使用call而使用反而增加了业务代码的维护成本,正常情况还是建议不要写出上面那段代码的坏味道,我们只要明白在什么时候可以用,什么可以不用就行,不要为了使用而使用,反而本末倒置。

但是有时候如果业务复杂,你想隔离业务的耦合,达到通用,call能帮你减少不少代码量

  • apply

image.png apply会立即执行该函数,如果传入的首个参数是null或者undefined,那么此时内部this指向的是window

另一种方法可以让函数立即执行,也能改变当前函数this指向

image.png

  • bind 这也是可以改变this指向,不过会返回一个新函数,我们常常在react中发现这样用bind显示绑定方案。

image.png

image.png

对比下以上两块代码,觉得没什么问题。此时第二块代码内部的this一定指向的window,而且内部访问style报错

那么我们改变下 image.png 这样是可以的,本质上就是一个fn的形参,内部this指向仍然是document.body

image.png

image.png

手写实现

bind

image.png 参考MDN

call

image.png 注意点:

  • this 是否是一个函数
  • 当context为null,undefined的时候,一定是指向window吗?node环境呢?
  • fn属性要保证唯一
  • 安全的执行函数
    • 如果用eval的话,eval是否能保证一定可以执行?
  • 尽量使用es5的语法来实现
  • 当前是否是严格模式

apply

image.png

vue2.0+ bind函数

image.png bind函数中主要是做了兼容性的处理,如果不支持原生的bind函数,则根据参数个数的不同分别使用call/apply来进行this的绑定,而call/apply最大的区别就是传入参数的不同,一个分别传入参数,另一个接受一个数组。

vue3.0+ this

image.png getCurrentInstance() 方法,获取当前组件的实例,通过 ctx 或 proxy 属性获得当前上下文,从而就能在setup中使用router和vuex.ArraygetCurrentInstance 方法去获取组件实例来完成一些主要功能,在项目打包后,会报错(不推荐使用)