当前位置: 首页 > Web前端 > JavaScript

js函数this指向问题分析

时间:2023-03-27 18:29:46 JavaScript

this指向问题的基本定义。最好查看正式文档。网上文章错误较多,容易误导人。相关内容的仓库地址:https://github.com/goblin-pitcher/steel-wheel-run/blob/master/%E6%98%93%E5%BF%98%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/this%E6%8C%87%E5%90%91%E7%9B%B8%E5%85%B3.mdthis指向相关定义这里主要讨论function函数和箭头函数的this的相关定义:function函数,参考mdn官方文档(this)大多数情况下,函数的调用方式决定了this的值(runtimebinding).this在执行过程中不能赋值,每次调用函数时this的值可能都不一样。箭头函数,参考mdn官方文档(Arrowfunction)箭头函数不会自己创建this,只会从自身作用域链的上层继承this如何理解箭头函数的this理解箭头的thisfunction,最好的办法就是看babel是怎么转义的。例子如下:原代码:constname='a'constobj={name:'b',f:()=>{console.log(this.name)},f0(){return()=>{console.log(this.name)}}}被转义为es5代码:var_this=void0;varname='a';varobj={name:'b',f:functionf(){console.log(_this.name);},f0:functionf0(){var_this2=this;返回函数(){console.log(_this2.name);};}};可以发现,箭头函数的转义就是在其上层作用域定义了_this对象,然后将箭头函数内部的this替换为_this。结合箭头函数this的定义,箭头函数不会创建自己的this,它只会从自身作用域链的上层继承this,我们不能把箭头函数中的this当作一个普通的对象,对于例如,将上面的代码改为:const_context=void0;常量名称='a';constobj={name:'b',f:functionf(){console.log(_this.name);},f0:functionf0(){const_context=this;返回函数(){console.log(_context.name);};}};obj.f0()()//输出bobj.f0.call({name:123})()//输出123将箭头函数转义成上述es5写法,效果还可以正确实现,使箭头函数的指向问题成为闭包问题。对于obj.f0()(),当执行obj.f0()时,obj对象会被赋值给_context。执行箭头函数时,首先从它的作用域链中获取_context,然后打印_context.name,所以输出obj.name,即bobj.f0.call({name:123})()输出123也是一样的作为函数函数的this。正如文档定义所说,function函数的this是在运行时绑定的,一般情况下很容易理解。例子如下constname='a'constobj={name:'b',c:{name:'c',f(){console.log(this.name)}}}obj.c.f()//f运行时,绑定运行环境obj.c,输出cconstf=obj.c.ff()//运行时,全局绑定运行环境,输出正常情况,如obj.c.f(),我们将f前面的对象视为运行环境(标红的部分),如果没有前面的对象,则视为运行环境作为全球。容易出错的例子constname='a'constobj={name:'b',c:{name:'c',f(){returnfunction(){console.log(this.name)}}}}obj.c.f()()//Outputaobj.c.f.call({name:'xxx'})()//这个例子最容易混淆的部分是obj.c.f()生成了一个函数,生成的函数会它使用obj.c.f或obj.c作为运行环境,然后输出相应的名称。在我看来,函数函数的this是在运行时绑定的。我们不妨把一个function函数的执行分为两部分:找到它的运行环境,绑定this来执行这个函数。根据上面的理解,obj.c.f()的执行用代码表示,结果如下//obj.c.f()constenv=obj.cconstfunc=obj.c.ffunc.call(env)根据针对这个思路,看obj.c.f()()的执行。首先,所有的操作都有自己的优先级,比如consta=1;console.log(a||3+1)//输出a的结果,即1对于a||3+1,加法的优先级高于Or,即先执行3+1,而然后执行a||4,a为真,即输出a的值。同样,对于obj.c.f()(),也可以看成是这样一个结构体(obj.c.f())(),把(obj.c.f())看成一个整体的方法,当(obj.c.f())()执行时,(obj.c.f())前面没有环境,所以(obj.c.f())()执行环境是全局的。转换成代码如下//obj.c.f()()//先执行(obj.c.f()),并生成一个函数//开始寻找(obj.c.f())的执行环境,就可以看(obj.c.f())前面什么都没有,所以环境是globalconstenv=global(obj.c.f()).call(env)回头看箭头函数,使用babel转义代码就明白了箭头函数,毫无疑问会出现问题。但是让我们用箭头函数的定义来理解它。箭头函数不会创建自己的this,它只是从其作用域链中的上一层继承this。结合定义,按照上面的分析方法分析如下代码constname='a'constobj={name:'b',f(){return()=>{console.log(this.name)}}}/***原公式obj.f()()分析如下:*constenv0=obj;*constf0=obj.f*constf1=f0.call(env0)*//f1是箭头函数,所以它的函数域使用它的上层*constenv1=env0*f1.call(env1)//输出obj.name,即b*/obj.f()()//b综合练习结合了上面函数函数和箭头函数this指向的理解,分析多层嵌套函数的输出(注解是辅助分析内容)constname='a'constobj={name:'b',c:{name:'c',f(){console.log('==>0::',this.name)returnfunction(){//const_context=thisconsole.log('==>1::',this.name)return()=>{//把它当作一个普通函数后,打印_context.nameconsole.log('==>2::',this.name)returnfunction(){//const_context=thisconsole.log('==>3::',this.name)return()=>{//把它当作一个普通函数后,打印_context.nameconsole.log('==>4::',this.name)}}}}}}}/***原公式为obj.c.f()()()()()(),解析如下:*constenv0=obj.c*constf0=obj.c.f*//公式优化为(f0.call(env0))()()()()*constf1=f0.call(env0)//output==>0::c*constenv1=global*//公式优化为(f1.call(env1))()()()*constf2=f1.call(env1)//output==>1::a*let_context=env1//f2是一个箭头函数,所以_context被分配给父环境env1*constenv2=_context;*//公式优化为(f2.call(env2))()()*constf3=f2.call(env2)//_context==env1==global,output==>2::a*const环境3=全球;*//公式优化为(f3.call(env3))()*constf4=f3.call(env3)//输出==>3::a*_context=env3*constenv4=_context;*f4.call(env4)//_context==env1==global,output==>4::a**综上所述,输出如下:*==>0::c*==>1::a*==>2::a*==>3::a*==>4::a*/obj.c.f()()()()()/***原来公式是obj.c.f().call({name:1})()()(),分析如下:*constenv0=obj.c*constf0=obj.c.f*//公式优化为(f0.call(env0)).call({name:1})()()()*constf1=f0.call(env0)//输出==>0::c*constenv1={name:1}*//公式优化为(f1.call(env1))()()()*constf2=f1.call(env1)//output==>1::1*let_context=env1//f2是一个箭头函数,所以_context被赋值给父环境env1*constenv2=_context;*//公式优化为(f2.call(env2))()()*constf3=f2.call(env2)//_context==env1=={name:1},Output==>2::1*constenv3=全局;*//表达式优化为(f3.call(env3))()*constf4=f3.call(env3)//输出==>3::a*_context=env3*constenv4=_context;*f4.call(env4)//_context==env1==global,output==>4::a**总结,输出如下:*==>0::c*==>1::1*==>2::1*==>3::a*==>4::a*/obj.c.f().call({name:1})()()()/***原公式为obj.c.f.call().call({name:1}).call({name:2}).call({name:3})(),解析如下:*constenv0=global//call没有参数,视为全局*constf0=obj.c.f*//公式优化为(f0.call(env0)).call({name:1}).call({name:2}).call({name:3})()*constf1=f0.call(env0)//输出==>0::a*constenv1={name:1}*//公式优化是(f1.call(env1)).call({name:2}).call({name:3})()*constf2=f1.call(env1)//output==>1::1*let_context=env1//f2是箭头函数,所以_context赋值给了父环境env1*//注意即使执行了.call({name:2}),env2还是_context,因为this在箭头函数*constenv2=_context;*//公式优化为(f2.call(env2)).call({name:3})()*constf3=f2.call(env2)//_context==env1=={name:1},输出==>2::1*constenv3={name:3};*//公式优化为(f3.call(env3)).call(env3)()*constf4=f3.call(env3)//output==>3::3*_context=env3*constenv4=_上下文;*f4.call(env4)//_context==env1==global,output==>4::3**综上所述,输出如下:*==>0::a*==>1::1*==>2::1*==>3::3*==>4::3*/obj.c.f.call().call({name:1}).call({name:2}).call({name:3})()以上内容先编写分析,然后在浏览器中运行,结果妥妥的按照这个思路一步步分析,即使嵌套再复杂,对应的可以逐步分析结果。