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

前端内训基础——this的理解

时间:2023-04-05 18:58:37 HTML5

前言:this的关键字是JavaScript中最复杂的机制之一。它有一个非常特殊的关键字,自动定义在所有函数的范围内。但即使是非常有经验的JavaScript开发人员也很难弄清楚它到底指向什么。——来源《你不知道的JavaScript上卷》在开始之前,我们先从几个问题开始。这个的定义是什么?绑定方式有几种,分别是什么?谁有更高的优先级?有几种方法可以改变这个方向,它们是什么?底层是如何实现的?this的定义是在运行时绑定的,而不是在编写时。它的上下文取决于函数中的各种条件。this的绑定与函数声明的位置无关,只与函数有关。调用方法。例如:functionfoo(){vara=2;this.bar();}functionbar(){console.log(this.a)}foo();//未定义的解析:其实从上面的代码可以清楚的看到,调用foo()的时候,可以理解为代码被解析成了window.foo()。此时this.bar()指向window。同样,bar()函数.a)打印的console.log(this)可以理解为console.log(window.a),但是真正的vara=2其实是在foo()的范围内,在当前窗口对象中不存在,只能输入undefined。总结:这其实就是调用函数时发生的绑定,它指向什么完全取决于调用函数的位置。1.了解调用位置functionbaz(){//当前调用栈:baz//因此当前调用位置为全局作用域console.log('baz')bar()//bar的调用位置}functionbar(){//当前调用栈为baz->bar//因此,当前调用位置在bar。console.log('bar')foo();//<--foo的调用位置}functionfoo(){//当前调用栈为baz->bar->foo//因此,当前调用位置为bar。console.log('foo')}baz();//<---baz的调用位置注意我们是如何从调用栈中分析出真正的调用位置的,它决定了this的绑定。2.绑定规则默认绑定隐式绑定显式绑定硬绑定的变体newbinding默认绑定函数foo(){console.log(this.a)}vara=2;富();解析:functionfoo(){//console.log(this.a)console.log(window.a)}//vara=2;window.a=2;//foo();window.foo();首先要明白的是,声明在全局范围内的变量会挂载到window对象中,除了es6的let和const声明,它们有临时死区。总结:代码中foo()是直接使用函数引用调用的,没有任何修饰,是默认的绑定。注意:如果使用严格模式,则不能使用全局对象进行默认绑定,因此this会绑定到undefinedfunctionfoo(){"usestrict"console.log(this.a)}vara=2;foo();//Cannotreadproperty'a'ofundefined详细信息:在严格模式下直接调用foo()不会影响默认绑定。functionfoo(){console.log(this.a)}vara=2;;(function(){"usestrict"foo();}())隐式绑定示例1:常见的隐式绑定functionfoo(){console.log(this.a)}varobj={a:2,foo:foo}obj.foo()解析://functionfoo(){//console.log(this.a)//}varobj={a:2,//foo:foo,foo(){//函数执行时,this指向的obj,console.log(obj.a)console.log(obj.a)}}obj.foo()例2:对象属性引用链只作用于上层或末层调用位置。functionfoo(){console.log(this.a)}varobj2={a:42,foo:foo}varobj1={a:2,obj2:obj2}obj1.obj2.foo()//42解析://functionfoo(){//console.log(this.a)//}varobj2={a:42,//foo:foo//我在最后一层,我不在乎谁是谁在你前面,IAllpointtoyou=>obj2foo(){//函数执行时,this指向obj2,console.log(obj2.a)console.log(obj2.a)}}varobj1={a:2,obj2:obj2}obj1.obj2.foo()//42示例3:this的隐式绑定丢失functionfoo(){console.log(this.a)}varobj={a:2、foo:foo}varbar=obj.foo//函数别名vara='window'bar()解析:functionfoo(){console.log(this.a)}//第一步varobj={a:2,//foo:foofoo(){/***运行时谁在调用函数*1.如果是obj.foo();一=2;因为当前这个点是运行时绑定。*2.如果是函数别名,此时其实在window.xx中,那么指向全局**/console.log(this.a)}}//第二步//如果执行obj.foo()直接;a=2//第三步//varbar=obj.foo//函数别名window.bar=function(){console.log(this.a)//this=>window}vara='Iamglobal'bar()虽然bar是对obj.foo的引用,但它实际上引用了foo本身的功能,所以应用默认绑定。总结:当函数引用有上下文对象时,隐式绑定规则将this绑定到这个上下文对象,显式绑定普通显式绑定函数foo(){console.log(this.a)}varobj={a:2}foo.call(obj)parsing:functionfoo(){console.log(this.a)}varobj={a:2,foo(){//借用这个方法console.log(this.a)}}foo.call(obj)then如何理解这个调用做了什么?让obj借用foo的方法。call的实现Function.prototype.call_=function(context,...args){varcontext=context||窗户;//1.对象是否传入?没有的是全局context.fn=this;//2.谁给我打电话,我就指向谁varresult=eval('context.fn(...args)');//3。使用eval把整个参数执行一次deletecontext.fn;//执行完我会删除之前的信息。返回结果}应用实现Function.prototype.call_=function(context,args){varcontext=context||窗户;//1、传入的是对象吗?没有的是全局context.fn=this;//2.谁给我打电话,我就指向谁varresult=eval('context.fn(...args)');//3。使用evalput执行整个参数deletecontext.fn;//执行后我会删除之前的信息returnresult}其实从代码中可以看出这两种实现方式是一样的,只是调用传递的是普通参数,apply传递的是一个数组。硬绑定显示绑定变量->bindfunctionfoo(){console.log(this.a)}varobj={a:2}varbar=function(){foo.call(obj)}bar();//2setTimeout(bar,1000);//2bar.call(窗口);//2硬绑定的原理就是不管后面怎么调用bar,总是手动调用obj上的foo。既然hardbinding等同于bind绑定,那么nativebind是如何实现的呢?Bindimplementation:Function.prototype.bind=function(context,...args){//1.必须传递一个函数,<我是挂载到函数原型的方法>if(typeofthis!=='function'){thrownewError('Youhavetopassafunction')}//2.存储当前指针对象varself=this;//3.返回一个函数varfbund=function(){//3-1使用apply方法判断绑定的原型对象,拼接outside函数和返回函数的arguments参数self.apply(thisinstanceofself?this:context,args.concat([...arguments]))}//4.当前Prototype存储,保存原型对象if(this.prototype){fbund.prototype=Object.create(this.prototype)}//返回一个硬绑定函数对象;returnfbund}应用场景:Wrap函数:接受参数并返回值functionfoo(soms){console.log(this.a,soms);returnthis.a+soms}varobj={a:2}varbar=function(){returnfoo.应用(对象,参数)}varb=bar(2)console.log(b);newbinding要想了解newbinding的原理,首先要了解调用new关键字时会发生什么。创建一个全新的对象这个新对象会被执行prototypelink这个新对象会绑定到函数调用的this上如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象Examplecode:functionFoo(a){this.a=a;}varbar=newFoo(2)console.log(bar.a)新函数的实现_new(ctor,...args){//1.必须passafunction,if(typeofctor!=='function'){thrownewError('Youhavetopassafunction')}//2.创建一个全新的对象letobj=newObject();//3.执行原型链接obj.__proto__=Object.create(ctor.prototype);//4.传递执行参数letres=ctor.apply(obj,[...args])letisObject=typeofres==='object'&&res!==null;让isFunction=typeofres==='函数';//5.判断构造函数是否返回对象\函数,如果返回对象,则执行对象\函数returnisObject||是功能?res:obj;}箭头函数functionfoo(){return(a)=>{console.log(this.a)}}varobj1={a:2};varobj2={a:3};varbar=foo.call(obj1)bar.call(obj2);分析:foo()内部创建的箭头函数在调用时会捕获foo()的this,由于foo()的this绑定到obj1,bar(箭头函数)的this也会绑定到obj1,而不能修改箭头函数的绑定。总结:需要知道的是,箭头函数根本没有自己的this,导致内部的this指向外部代码的this。这个点在定义的时候就已经确定了,调用的时候不会指向其执行环境的(可变)对象。3.绑定优先级判断首先要判断的是:默认绑定优先级最低。哪个具有更高的优先级,隐式绑定或显式绑定?functionfoo(){console.log(this.a)}varobj1={a:2,foo:foo}varobj2={a:3,foo:foo}obj1.foo();//2obj2.foo();//3obj1.foo.call(obj2);//3obj2.foo.call(obj1);//2总结:显式绑定具有更高的优先级。哪个具有更高的优先级,隐式绑定或新绑定?functionfoo(some){this.a=some}varobj1={foo:foo}varobj2={}obj1.foo(2);console.log(obj1.a)//2varbar=newobj1.foo(4)console.log(obj1.a)//2console.log(bar.a)//4总结:新绑定具有高优先级。哪个具有更高的优先级,显式绑定或新绑定?functionfoo(some){this.a=some;}varobj1={};varbar=foo.bind(obj1)bar(2);console.log(obj1.a)//2varbaz=newbar(3);console.log(obj1.a)//2console.log(baz.a)//3总结:新绑定具有高优先级。结论:新绑定>显式绑定>隐式绑定>默认绑定。结语以上就是关于这方面的全部知识点。如果你想了解一切,你需要多写多读。下面我们做几个关于这个的问题来巩固一下刚刚读到的内容。4.面试问题示例代码1:vara=1functionfoo(){vara=2functioninner(){console.log(this.a)//?}inner()}foo()示例代码2:varobj={a:1,foo:function(b){b=b||this.a返回函数(c){console.log(this.a+b+c)}}}vara=2varobj2={a:3}obj.foo(a).call(obj2,1)obj.foo.call(obj2)(1)示例代码3:functionfoo1(){console.log(this.a)}vara=1varobj={a:2}varfoo2=function(){foo1.call(obj)}foo2()foo2.call(window)更多信息请参考这个面试题