当前位置: 首页 > 科技观察

你真的了解JavaScript中的“this”吗?

时间:2023-03-13 15:52:41 科技观察

“这个”是什么?当一个函数被调用时,会创建一个执行环境,这个会在运行时根据函数的执行环境进行绑定。它允许函数在上下文中内部引用执行变量,使函数式编程更加优雅和简洁。看看下面的代码,想想为什么不同的调用方式会打印出不同的结果。vara=10constobj={a:20,foo:function(){console.log(this.a)returnfunction(){console.log(this.a)}}}obj.foo()()//2010constfn=obj.foofn()//10其实很简单,因为不同的调用方法的this指向不同的点。为什么this指向不同的函数调用方法?这是什么决定的?现在让我们带着问题开始深挖这个问题吧!默认绑定“this”的约束规则。默认绑定规则下,函数的运行环境为全局环境,this默认指向Window。默认的绑定规则如下:1.this指向Window的全局函数。在全局函数中直接打印this,可以看到this指向了Window。2.独立函数调用这里指的是Window的独立函数调用,即直接调用函数,如foo()。functionfoo(){console.log(this);}foo()其中foo默认链接到Window,相当于window.foo()。根据函数的隐式绑定规则,谁调用谁就指向谁。这里this指向Window。结果如下:同理,如果嵌套函数中直接调用的函数也是独立函数调用,那么this也指向Window:vara=10varobj={a:20,foo:function(){console.log(这个);//{a:20,foo:?}console.log(this.a);//20函数son(){console.log(this);//窗口console.log(this.a);//10}//独立函数调用son()}}obj.foo()上面代码中,子函数son也嵌套在对象obj的方法foo中。直接调用子方法的时候,子函数里的this指向Window,所以子函数里this.a的结果就是全局变量a,也就是10,如果要用变量a怎么办在子函数中的obj中?只需将此对象分配给另一个变量并在子方法中引用此变量:vara=10varobj={a:20,foo:function(){constthat=thisfunctionson(){console.log(that.a);//20}son()}}obj.foo()3.自执行函数调用,this指向窗口自执行函数。顾名思义,就是定义函数后自动调用的函数。指向以下代码://exp1(function(){console.log(this);//Window})()//exp2vara=10functionfoo(){(functionson(that){console.log(this);//窗口console.log(that);//{a:20,foo:?}})(this)}varobj={a:20,foo:foo,}obj.foo()在上面的代码foo函数内部嵌套了一个自执行函数son,这个insideson指向Window。这里所指的原理类似于独立的函数调用,即先声明一个son方法,然后通过son()执行函数。如果想获取son中上层对象obj的变量,可以在调用时将这个点作为参数传递给自执行函数son。4.闭包中的this指向WindowClosure可以理解为函数内部定义的函数,可以访问其他函数的内部变量。当我们查看闭包中的this点时,我们可以看到this指向Window。vara=10varobj={a:20,foo:function(){varsum=this.count+10console.log(this.a);//20returnfunction(){console.log(this.a);//10returnsum}}}obj.foo()()上面代码中,foo函数的第一个this.a的this.a指向obj对象,所以结果为20。return函数调用指向Window,结果为10。obj.foo()()可以理解为:constfn=obj.foo()fn()fn是obj.foo()返回的函数。fn函数是独立调用的,this指向Window。隐式绑定当函数作为方法调用时,this指向函数的直接父对象,称为隐式绑定。在隐式绑定规则中,this被认为指向调用该函数的任何人,并将指向该函数的直接父级。比如obj.foo()中foo函数中的this指向obj,obj1中的foo函数。obj2.foo()指向obj2。vara=10functionfoo(){console.log(this.a);}varobj={a:20,foo:foo,obj2:{a:30,foo:foo}}//exp1foo()//10//exp2obj.foo()//20//exp3obj.obj2.foo()//30上面的代码也是对foo函数的调用。调用方式不同,结果不同。'exp1'中的foo被独立函数直接调用,所以this指向Window,结果为10;“exp2”中的调用方法是obj。foo(),foo函数的this指向上级调用对象obj;结果是20。'exp3'中的foo函数的直接上级是obj2,所以结果是30。.这种做法很容易导致我们的项目出现bug,但也很常见。1.隐式绑定函数被分配给没有指向this的变量。在下面的代码中,obj下的foo值其实是foo函数的地址信息,并不是真正的foo函数。当对象。调用foo(),这是隐式绑定到obj的。当varfn=obj.foo给fn赋值一个函数。相当于把foo函数的地址赋值给fn。此时fn与obj没有关联,所以这里fn()的运行环境是全局环境,this指向Window,this的结果a为10。vara=10varobj={a:20,foo:function(){console.log(this.a);}}functionbar(fn){fn()}bar(obj.foo)//102、隐式将绑定的函数作为参数传递给函数,this点丢失。当一个隐式绑定的函数直接作为参数传递给另一个函数时,这个绑定就会丢失,从而指向全局Window。obj.foo作为参数传递给bar函数后,this.a的结果为10。这里bar(obj.foo)等同于varfn=obj.foo;栏(fn)。vara=10varobj={a:20,foo:function(){console.log(this.a);}}functionbar(fn){fn()}bar(obj.foo)//103.内置对象setTimeout和setInterval函数的隐式绑定丢失。内置函数setTimeout和setInterval的this默认指向Window。//exp1setTimeout(function(){console.log(this);//Window},1000)//exp2vara=10varobj={a:20,foo:function(){console.log(this.a);//10}}setTimeout(obj.foo,1000)对了,当setTimeout或者setInterval的第一个参数是箭头函数的时候,这个会指向上层的函数执行环境。代码如下:vara=10varobj={a:20,foo:function(){console.log(this.a);//20setTimeout(()=>{console.log(this.a);//20},1000)setTimeout(function(){console.log(this.a);//10},1000);}}obj.foo()显式绑定当我们要将函数绑定到指定的对象上时,可以使用call、apply、bind等方法手动改变this的方向,即显式绑定。在下面的代码中,分别使用call、apply和bind说明了将foo显式绑定到p对象的方法。显式绑定的call和apply方法会在显式绑定后直接调用,而显式绑定this到bind的方法则需要手动调用。varobj={a:20,foo:function(){console.log(this.a);}}varp={a:30,}obj.foo()//20obj.foo.call(p)//30obj.foo.apply(p)//30constfn=obj.foo.bind(p)fn()//30关于硬绑定,显式绑定可以帮我们把this改成指定的对象,但是不能解决隐式绑定缺失的问题,例如:vara=10functionfoo(){console.log(this.a);}varobj={a:20,foo:foo}varp={a:30}functionfunc(fn){fn()}func.call(p,obj.foo)//10在上面的代码中,调用将this绑定为指向p对象,但最终this指向Window。此时,我们可以通过硬绑定来解决这个问题。vara=10functionfoo(){console.log(this.a);}varobj={a:20,foo:foo}varp={a:30}functionfunc(fn){fn()}letbar=function(){foo.call(p)}bar()//30“new”绑定new绑定是我们常用的方法。事实上,我们可以创建一个构造,然后创建一个新的实例对象。这时,this指向新的实例对象。当我们认识彼此时,我们主要做以下事情:a.创建一个新对象B.让this指向新对象,执行构造函数c。设置新对象的proto属性指向构造好的原型对象d。判断构造返回类型。如果是,则返回新对象。如果是引用类型,则返回该类型的对象。首先创建“Person”这个结构体,然后通过“new”创建一个“zhangsan”的实例对象。在“zhangsan”的“foo”函数中,“this”指向“zhangsan”的实例。functionPerson(name,age){this.name=namethis.age=agethis.foo=function(){console.log(this.name);}}constzhangsan=newPerson('zhangsan',18)console.log(zhangsan)//{name:'zhangsan',age:18,foo:?}zhangsan.foo()//zhangsan处于严格模式,“这个”指向一个问题。1.独立调用函数的内部“this”是“undefined”functionfoo(){“usestrict”console.log(this);undefined}foo()2.在“call()”和“apply()”中,这始终是它们的第一个参数vara=10varobj={a:20,foo:function(){"usestrict"console.log(这);}}//为空时|undefined,在非严格模式下,this指向windowobj.foo.call(null)//nullobj.foo.call(undefined)//undefinedobj.foo.apply(null)//nullobj.foo.apply(undefined)//undefinedvarfn=obj.foo.bind(null)fn()总结这是一个比较复杂的知识点。当然,如果我们真正理解了this的原理,遇到this指出的问题就很简单了。如果我们明白了这一点,不仅可以为前端面试加分,也有利于我们的发展和学习。总结一下,约束如下:默认绑定,this指向全局Window。不要忘记隐藏绑定的丢失。它显示绑定。他通过调用、应用和绑定来改变这个方向。new绑定构造了一个new的实例,this指向new的实例对象。