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

JavaScript必须掌握的基础知识----this

时间:2023-04-02 14:01:33 HTML

thisthis是我们写代码时最常用的关键字之一,即便如此,它也是JavaScript中最头疼的关键字。那么这到底是什么?如果你了解执行上下文,那么你就会知道这其实是执行上下文对象的一个??属性:executionContext={scopeChain:[...],VO:{...},this:?执行上下文中有三个重要的属性,作用域链(scopeChain)、变量对象(VO)和this。this是在进入执行上下文时即函数执行时确定的,在运行过程中不允许修改,是永久的。全局代码中的this在全局代码中是不变的,this始终是全局对象本身。变量a=10;this.b=20;window.c=30;console.log(this.a);console.log(b);console.log(this.c);console.log(this===window)//true//由于这是全局对象窗口,所以上面的a、b、c相当于给全局对象添加了相应的属性。如果我们试图在代码运行时修改this的值,将会抛出一个错误:this={a:1};//UncaughtSyntaxError:Invalidleft-handsideinassignmentconsole.log(this===window)//函数代码中的this最容易被我们在函数代码中使用this搞糊涂了,这里主要分析函数中的this代码。上面我们说this的值是在进入当前执行上下文的时候确定的,也就是函数执行的时候,在执行前就确定了。但是对于同一个函数,作用域中的this点可能完全不同,但不管怎样,运行时函数的this点是不变的,不能赋值。functionfoo(){console.log(this);}foo();//windowvarobj={a:1,bar:foo,}obj.bar();//obj函数中的this有很多指针,可以是全局对象,也可以是当前对象,也可以是任意对象,当然要看函数是怎么调用的。在JavaScript中调用函数有几种方法:作为函数调用、作为对象属性调用、作为构造函数调用、使用apply或call调用。下面我们将根据这些调用方式一一讨论这个的含义。Callasafunction什么叫做函数调用:是一个独立的函数调用,没有任何修饰符。functionfoo(){console.log(this===window);//真this.a=1;控制台日志(b);//2}varb=2;foo();console.log(a);//1上面代码中,this绑定到全局对象window。this.a相当于在全局对象上加了一个属性a。在严格模式下,独立的函数调用,this的绑定不再是window,而是undefined。functionfoo(){“使用严格”;控制台日志(这个===窗口);//falseconsole.log(this===undefined);//真}foo();这里要注意,如果函数调用是在严格模式下,而内部代码执行是在非严格模式下,this仍然会默认绑定到window上。functionfoo(){console.log(this===window);//true}(function(){"usestrict";foo();})()调用this并指向函数内部的独立函数who?函数foo(){函数bar(){this.a=1;控制台日志(这个===窗口);//真}bar()}foo();控制台日志(一);//1上面的代码中,函数里面的函数是独立调用的,此时this还是绑定到window上的。总结:当一个函数作为一个独立的函数被调用时,内部的this默认绑定(指向)全局对象window,但是在严格模式下有所不同,this绑定为undefined。作为对象属性调用vara=1;varobj={a:2,foo:function(){console.log(this===obj);//真console.log(this.a);//2}}obj.foo();上面代码中foo属性的值是一个函数。这里foo被称为对象obj的方法。foo被调用为object。方法调用。此时this被绑定到当前调用该方法的对象上。这是obj对象。另一个例子:vara=1;varobj={a:2,bar:{a:3,foo:function(){console.log(this===bar);//真console.log(this.a);//3}}}obj.bar.foo();遵循上述规则对象。财产。这里的对象是obj.bar。此时foointernalthis绑定到obj.bar。所以this.a是obj.bar.a。让我们看另一个例子:vara=1;varobj={a:2,foo:function(){console.log(this===obj);//falseconsole.log(this===window);//trueconsole.log(this.a);//1}}varbaz=obj.foo;baz();在这里,foo函数是对象obj的一个方法。但它被分配给变量baz。baz调用时,相当于独立调用了foo函数,所以内部this绑定了window。使用apply或call在函数原型上调用apply和call作为方法。它可以在函数内部改变this的指向。vara=1;functionfoo(){console.log(this.a);}varobj1={a:2}varobj2={a:3}varobj3={a:4}varbar=foo.bind(obj1);bar();//2this=>obj1foo();//1this=>windowfoo.call(obj2);//3this=>obj2foo.call(obj3);//4this=>obj3当函数foo作为一个独立的函数被调用时,this绑定到全局对象window,当用bind、call或apply方法调用时,this分别绑定到不同的对象。作为构造函数调用vara=1;functionPerson(){this.a=2;//this=>p;}varp=newPerson();控制台日志(p.a);//2上面代码中,构造函数Person里面的this被绑定为Person的一个实例。总结:当我们要判断当前函数内部的this绑定时,可以遵循以下原则:函数是通过new操作符调用的吗?如果是,这将绑定到新创建的对象varbar=newfoo();//这=>酒吧;函数是call还是apply调用的?如果是,则绑定到指定对象foo.call(obj1);//this=>obj1;foo.apply(obj2);//这个=>obj2;是通过对象调用的函数。方法?如果是,则this绑定到当前对象obj.foo();//这个=>对象;函数是独立调用的吗?如果是这样,这将绑定到全局对象。富();//this=>this1在windowDOM事件处理程序中)。事件绑定点我//事件绑定函数handleClick(e){console.log(this);//点我}document.getElementById('btn').addEventListener('click',handleClick,false);//点我document.getElementById('btn').onclick=handleClick;//点我根据上面的代码,我们可以得出:给DOM元素添加事件,事件会绑定到当前DOM对象上。2).内联事件点击我点击我functionhandleClick(e){console.log(this);//window}//第二个按钮打印clickme我觉得内联事件可以这样理解://伪代码clickmeclickI这样我们就可以理解为什么其中一个内联事件上面的代码指向窗口,另一个指向当前的DOM元素。(当然,浏览器处理内联事件就不是这样了)timer中的this指向哪里呢?functionfoo(){setTimeout(function(){console.log(this);//窗口},1000)}foo();另一个例子varname="chen";varobj={name:"erdong",foo:function(){console.log(this.name);//erdongsetTimeout(function(){console.log(this.name);//chen},1000)}}obj.foo();到这里我们可以看到,函数foo内部的this指向了调用它的对象,即:obj。定时器中的this指向window。那么有什么方法可以将计时器中的this绑定到将它包装为同一个对象的函数吗?1).使用闭包:varname="chen";varobj={name:"erdong",foo:function(){console.log(this.name)//erdongvarthat=this;setTimeout(function(){//that=>objconsole.log(that.name);//erdong},1000)}}obj.foo();利用闭包的特性,函数内部的函数可以访问定时器中的含义和访问当前词法作用域变量,此时定时器中的就是包装它的函数中绑定到this的对象。下面我们将介绍使用ES6的箭头函数来实现这个功能。当然这里也可以使用bind:varname="chen";varobj={name:"erdong",foo:function(){console.log(this.name);//erdongsetTimeout(function(){//this=>objconsole.log(this.name);//erdong}.bind(this),1000)}}obj.foo();如果使用null或undefined作为this的绑定对象传递给call、apply或bind,这些值在调用时会被忽略,this绑定的实例对应上述规则。vara=1;functionfoo(){console.log(this.a);//1this=>window}varobj={a:2}foo.call(null);vara=1;functionfoo(){console.log(this.a);//1this=>window}varobj={a:2}foo.apply(null);vara=1;functionfoo(){console.log(this.a);//1this=>window}varobj={a:2}varbar=foo.bind(null);bar();bind也可以实现函数柯里化:functionfoo(a,b){console.log(a,b);//23}varbar=foo.bind(null,2);bar(3);更复杂的例子:varfoo={bar:function(){console.log(this);}};foo.bar();//foo(foo.bar)();//foo(foo.bar=foo.bar)();//窗口(false||foo.bar)();//窗口(foo.bar,foo.bar)();//window上面代码中:foo.bar()是对象的方法调用,所以this绑定到了foo对象上。(foo.bar)()前面()里的内容没有计算,所以还是foo.bar()(foo.bar=foo.bar)()前面()里的内容计算为function(){控制台.log(这个);}所以这里是匿名函数的自执行,所以this绑定了全局对象window,下面两个实例同上。最好这样理解:(foo.bar=foo.bar)括号里的表达式是先计算,再赋值,再返回值。(false||foo.bar)()执行括号内的表达式,判断前者是否为真,如果为真则不计算后者,如果为假则计算后者并返回后者的值.(foo.bar,foo.bar)括号中表达式的行为分别对“,”运算符两边求值,然后返回“,”运算符后的值。箭头函数中的this箭头函数是ES6中的新语法。有两个函数:比较简洁的函数本身不绑定this代码格式为://普通函数functionfoo(a){//......}//箭头函数varfoo=a=>{//......}//如果没有参数或者参数有多个varfoo=(a,b,c,d)=>{//......}我们使用之前的普通函数函数This绑定需要根据this函数的调用方式来确定其内部this的绑定对象。并且往往因为调用链多或者找不到真正的调用者,导致指向this的指针不明确。箭头函数出现后,其内部的this点就不需要通过调用方法来确定了。箭头函数有几个特点(与普通函数的区别)箭头函数不绑定this。它仅从作用域链的上一级继承。箭头函数不绑定实参,使用reset参数获取实参个数。箭头函数是匿名函数,不能用作构造函数。箭头函数没有原型属性。不能使用yield关键字,所以箭头函数不能用作函数生成器。这里我们只讨论箭头函数中的this绑定。用一个例子来比较this在普通函数和箭头函数中的绑定:varobj={foo:function(){console.log(this);//obj},bar:()=>{console.log(this);//窗口}}obj.foo();obj.bar();在上面的代码中,还通过对象调用了一个函数。是箭头函数。一句话总结箭头函数中的this绑定:我个人说会从作用域链的上层继承this,不是很正确。范围存储函数当前执行上下文和所有父执行上下文的变量对象的集合。所以范围链中没有this。应该说这是在作用域链上层对应的执行上下文中继承的。箭头函数中的this继承自thisvarobj={foo:function(){console.log(this);//obj},bar:()=>{console.日志(这个);//窗口}}obj.bar();上面代码中obj.bar执行时的作用域链为:scopeChain=[obj.bar.AO,global.VO]根据上面的规则,此时bar函数中的this指向global中的this执行上下文,即:window。让我们看另一个例子:varobj={foo:function(){console.log(this);//objvarbar=()=>{console.log(this);//对象}bar();}}obj.foo();在一个普通的函数中,内部的this在bar执行时被绑定为全局对象,因为它是作为一个独立的函数被调用的。但是在箭头函数中,它绑定到obj。在父函数中绑定到与this相同的对象。此时它的作用域链是:scopeChain=[bar.AO,obj.foo.AO,global.VO]到这里我们就差不多知道箭头函数中的this绑定了。继续看例子:varobj={foo:()=>{console.log(this);//窗口varbar=()=>{console.log(this);//窗口}bar();}}obj.foo();为什么此时又指向了window呢?我们还查看bar执行时的作用域链:scopeChain=[bar.AO,obj.foo.AO,global.VO]当我们在bar函数中找到这个绑定时,我们会在foo函数绑定中找到这个。因为它继承自它。这时候foo函数也是一个箭头函数。此时foo中的this绑定的是window,而不是调用它的obj对象。因此,bar函数中的this绑定也是全局对象window。我们在上面的计时器中回顾这个例子:varname="chen";varobj={name:"erdong",foo:function(){console.log(this.name);//erdongsetTimeout(function(){console.log(this);//chen},1000)}}obj.foo();这时候我们可以很方便的把timer中的this和foo中的this绑定为同一个Object:varname="chen";varobj={name:"erdong",foo:function(){//this=>objconsole.log(this.name);//erdongsetTimeout(()=>{//this=>thisinfoo=>objconsole.log(this.name);//erdong},1000)}}obj.foo();写在最后。文中如有错误,请留言指正,万分感谢。喜欢它,让我们一起学习进步。GitHub