JS中this指向问题
写这篇文章的原因是因为在JS中this也是一个比较难捕捉的一个知识点,并且在面试中会出现在笔试题或者机试题中。为了加深印象,重新整理作为记录。
正文开始...
开始之前大家带着问题来看,跟着文章思路一起思考:
- 什么时候产生的this
- 箭头函数中的this(ES6+)
- 改变this指向的API有那些
this是什么
- 全局this
首先我们新建一个test.html文件,然后在body中加入script标签,输出 console.log(this, Object.getPrototypeOf(this));
【浏览器执行环境下】打开浏览器的控制台,我们看到输出的都是 Window 对象,那么在浏览器环境下,全局this是一个window对象
【node环境下执行】当我们打开编辑器,执行
node test.js时
在
node环境下,全局的this居然是一个{}对象
- 严格模式下函数内部 this(在js文件顶部添加
'use strict'使用严格模式)
在
严格模式下函数内部会是undefined,并且访问test会直接报错.
那么为什么use strict严格模式下全局this无法访问。
原因:
- 未提前申明的变量不能使用,会报错
- 不能用
delete删除对象的属性 - 定义的变量名不能重复申明
- 函数内部的
this不再指向全局对象
通常会看到this的指向并不都是指向全局对象,而是动态变化的。
以上例子可得出:
- 非严格模式普通函数
this指向 => 在普通函数内部this指向的是window对象 2.构造函数的this指向 => 构造函数内部的this居然就是实例化的那个对象obj3.对象定义的内部函数 => 肯定publicName肯定是放弃懒惰,内部的this也是指向obj
- 接下来我们看下箭头函数
箭头函数不是没有自己的this吗,而且这里是obj2.getName()这不是一个隐式调用吗?应也是obj2这个对象才对,但是并不是,当改成箭头函数后,内部的this居然变成了全局的window对象了
我们看下babel对上面一段代码编译成es5的代码
左边为 es6代码,输出后为右边代码。
其实箭头函数是非常迷惑人的,而且外面是一个被调用的是一个对象,所以时常会给人一种幻觉,我们常听到一句this指向的是被调用的那个对象,那么这里箭头函数的this指向的是window,而const定义的变量会被转换成var。
那怎么能让getName指向的是本身自己的obj2呢
当把箭头函数改成普通函数,这个普通函数内部的
this就指向obj3自己了
另一种情况:
此时你会发现打印的是
放弃懒惰 outer
此时会发现this指向的是window,也就是说指向的那个被调用者,那被调用者是谁?
上面那段代码同等于下面,你仔细看
所以你现在是不是很清晰明白
this指向的也是被调用的那个对象window了。
上一篇文章闭包已经介绍过作用域,这里不在赘述,可查阅Javascript基础系列之闭包
**注意:**必须在非严格模式下,此时的this才会指向window。
总结下:
非严格模式下
- 普通函数内部的
this指向的是window对象 - 构造函数内的
this指向的是实例化的那个对象 - 普通申明的对象,如果调用的方法是箭头函数,那么内部
this指向的是全局对象,如果不是那么指向的是被调用本身的那个对象
接下来我们看下面试题,加深理解:
obj.b()的调用实际上在之前例子已经有讲,b方法是一个普通方法,内部this指向的就是被调用的obj对象,所以此时内部访问的a属性就是对象obj
var objb = obj.b,当我们看到这样的代码时,其实这段代码可以拆分以下
本质上就是将对象
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的赋值有异曲同工之笔
本质上就在
objc动态的新增了一个属性b,而这个属性b赋值了一个方法,也就是下面这样
如果是
const t = objc.b,至此你会发现,当我们执行t()时,此时打印的却是2那是因为const t定义的变量会编译成var从而t变量变成一个全局的window对象下的属性,本质上等价下面
- 多层对象嵌套下的
this
以上的结果是3,实际上我们从之前案例中明白,非严格模式下
this指向被调用那个对象
所以你可以把上面那段代码看成下面这样
改变this指向
改变this指向方法: call\apply、bind,先介绍使用
- call
正因为在计算属性中用了
call所以在config.js中才能访问外部methods的方法,当我们为了使用call而使用反而增加了业务代码的维护成本,正常情况还是建议不要写出上面那段代码的坏味道,我们只要明白在什么时候可以用,什么可以不用就行,不要为了使用而使用,反而本末倒置。
但是有时候如果业务复杂,你想隔离业务的耦合,达到通用,call能帮你减少不少代码量
- apply
apply会立即执行该函数,如果传入的首个参数是null或者undefined,那么此时内部this指向的是window
另一种方法可以让函数立即执行,也能改变当前函数this指向
- bind
这也是可以改变
this指向,不过会返回一个新函数,我们常常在react中发现这样用bind显示绑定方案。
对比下以上两块代码,觉得没什么问题。此时第二块代码内部的this一定指向的window,而且内部访问style报错
那么我们改变下
这样是可以的,本质上就是一个
fn的形参,内部this指向仍然是document.body
手写实现
bind
call
注意点:
- this 是否是一个函数
- 当context为null,undefined的时候,一定是指向window吗?node环境呢?
- fn属性要保证唯一
- 安全的执行函数
- 如果用eval的话,eval是否能保证一定可以执行?
- 尽量使用es5的语法来实现
- 当前是否是严格模式
apply
vue2.0+ bind函数
bind函数中主要是做了兼容性的处理,如果不支持原生的bind函数,则根据参数个数的不同分别使用call/apply来进行this的绑定,而call/apply最大的区别就是传入参数的不同,一个分别传入参数,另一个接受一个数组。
vue3.0+ this
getCurrentInstance() 方法,获取当前组件的实例,通过 ctx 或 proxy 属性获得当前上下文,从而就能在setup中使用router和vuex.ArraygetCurrentInstance 方法去获取组件实例来完成一些主要功能,在项目打包后,会报错(不推荐使用)