JS中的this是个老生常谈的问题,因为它不是一个确定的值,而且在不同的情况下它有不同的方向,所以经常让人迷惑。本文将谈谈我自己对此的理解。这到底是什么?其实这是一个指针,它表示当前的执行环境,可以用来对当前的执行环境进行一些操作。因为它表示执行环境,所以在定义这个变量时,它的真实值是未知的,只能在运行时确定它的值。同一段代码,不同的执行方式,其thispoint可能不同。我们来看看下面的代码:functionfunc(){this.name="小小飞";控制台日志(这个);//看看这是什么}这个方法很简单,就是给this加上一个name属性,我们把这个方法复制到Chrome调试工具中看看结果:上图中,我们直接调用了func(),发现this指向window,name属性被添加到window。接下来我们改一下调用方式,我们使用newfunc()来调用:我们看到输出了两个func{name:"XiaoXiaofei"},一个是我们new返回的对象,一个是console里面的方法.这两个值是一样的,也就是说方法中的this指向new返回的对象,而不是前面例子中的window。这是因为当你使用new调用一个方法时,这个方法实际上是作为构造函数使用的。这时候this指向的就是new的对象。下面分别说明一下情况。使用new调用时,此规则指向新对象。这个规则其实是JS面向对象的一部分。JS用很曲折的方式来支持面向对象。当你用new来执行一个函数时,这个函数就变成了一个类,new关键字会返回这个类的一个实例给你,这个函数就充当了一个构造函数。作为面向对象的构造函数,它必须具备为实例初始化属性的能力,所以构造函数中必须有某种机制来操作生成的实例,而这个机制就是这个。让this指向生成的实例,通过this来操作实例。有关面向对象JS的更详细解释,请参阅本文。这个特性还有一些神奇的用途。一个函数可以直接调用,也可以用new调用,那么如果我只想让用户通过new调用,有什么办法吗?下图摘自Vue的源码:Vue巧妙的利用了this的特性,通过判断this是否是Vue的实例来检测用户是通过new调用还是直接调用。当没有明确的来电者时,这指向窗口。这个其实在第一个例子中就提到了,这里没有明确调用者,this指向window。这里再说一个例子,函数中的函数,this指向谁?functionfunc(){functionfunc2(){console.log('this:',this);//这里指向谁?}func2();}我们执行一下看看:直接执行:用new执行:我们发现无论是直接执行还是用new执行,this的值都指向了window。直接执行的时候很好理解,因为没有明确的caller,那么这自然就是window了。需要注意的是,在使用new时,只有newfunc是构造函数,它的this指向new出来的对象,它里面的函数的this还是指向window。当有明确的来电者时,this指向来电者。看这个例子:varobj={myName:"小小飞",func:function(){console.log(this.myName);}}obj.func();//小小飞上面的例子很好理解,因为调用者是obj,所以func中的this指向obj,this.myName就是obj.myName。其实这个和上一个是可以结合的。当没有明确的调用者时,隐式调用者是window,所以常说this总是指向调用者。让我们稍微改变一下这个例子:}}varanotherFunc=obj.func;anotherFunc();//输出是什么?这里的输出应该是“大哥”,因为anotherFunc的函数体虽然和obj.func一样,但是执行环境不同。其实它并没有明确的调用者,或者调用者是window。这里的this.myName其实就是window.myName,也就是“飞哥”。我们再把这个例子改一下:}}varanotherFunc=obj.func;anotherFunc();//注意这里的输出是undefined这次我们只是把第一个var改成let,但是我们的输出已经变成undefined了。这是因为let和const定义的变量,即使在最外层,也不会成为window的属性,只有var定义的变量才会成为window的属性。箭头函数不绑定this这句话的意思是箭头函数本身是没有this的。当箭头函数声明为确定this时,此时,它会直接使用当前作用域的this作为自己的this。在前面的例子中,我们把函数改成了箭头函数:varmyName="飞大哥";varobj={myName:"小飞",func:()=>{console.log(this.myName);}}varanotherFunc=obj.func;obj.func();//大哥anotherFunc();//大飞上面代码中obj.func()的输出也是“大哥”,因为在创建obj的时候声明了箭头函数。这个时候箭头函数会搜索当前作用域,因为obj是对象,不是作用域,所以这里的作用域是window,this也是window。再看一个例子:varmyName="BigBrother";varobj={myName:"小飞",func:function(){return{getName:()=>{console.log(this.myName);}}}}varanotherFunc=obj.func().getName;obj.func().getName();//小小飞anotherFunc();//小小飞的两个输出是“小小飞”,Obj.func().getName()输出“小小飞”很好理解。这里箭头函数是在obj.func()的返回值中声明的。这时候他的this其实就是func()的this,因为He是被obj调用的,所以this指向obj。那为什么anotherFunc()的输出也是“小小飞”呢?这是因为anotherFunc()的this输出实际上是在分配anotherFunc时确定的:varanotherFunc=obj.func().getName;其实getName箭头是在obj.func()先执行的时候执行的函数声明时,箭头函数的this应该是当前作用域的this,也就是调用了func()中的thisfunc()通过obj,所以this指向obj调用anotherFunc的时间。其实这个已经确定了,就是obj,最后输出的是obj.myName。让我们看看构造函数中的箭头函数。我们前面提到了构造函数中的函数。直接调用的时候this指向window,但是如果这个函数是箭头函数呢:varmyName="大哥";functionfunc(){this.myName="小小飞";constgetName=()=>{console.log(this.myName);}getName();}newfunc();//输出是什么?这里输出的是“小小飞”,原理还是一样的。箭头函数声明的时候,this就确定为当前作用域的this,这里就是func的作用域,和func的this一样指向新的实例。如果不用new,直接调用,这里的this指向window。在DOM事件回调中,this指向事件函数绑定的对象func(e){console.log(this===e.currentTarget);//始终为真console.log(this===e.target);//如果target等于currentTarget,则为true}constele=document.getElementById('test');ele.addEventListener('click',func);currentTarget指的是绑定事件的DOM对象,target指的是给触发器事件的对象。在DOM事件回调中,this总是指向currentTarget。如果触发事件的对象刚好是绑定事件的对象,即target===currentTarget,this也会顺便指向target。如果回调是箭头函数,则this是声明箭头函数时作用域的this。在严格模式下,这是未定义的functionfunc(){"usestrict"console.log(this);}func();//输出是undefined注意this在strict模式下是undefined的意思是在函数体内部,如果本身即使在全局范围内,this仍然指向window。......这可以更改吗?这个可以改,call和apply都可以改这个,ES6还加了bind函数。使用call和apply修改thisconstobj={myName:"大哥",func:function(age,gender){console.log(`我的名字是${this.myName},我的年龄是${age},我我是${gender}`);}}constobj2={myName:"小小飞"}obj.func.call(obj2,18,"帅哥");//我叫小小飞,今年18岁,是个帅哥。注意上面输出的名字是“小小飞”,也就是obj2.myName。正常情况下,直接调用obj.func()输出的名字应该是obj.myName,也就是“大哥大”。但是如果用call来调用,call的第一个参数就是手动指定的this。我们指定为obj2,那么函数中的this.myName其实就是obj2.myName。apply方法与call方法类似,只是函数参数的形式不同。apply调用应该这样写,函数参数放在数组或类数组中:obj.func.apply(obj2,[18,"Handsome"]);//我叫小飞,今年18岁,是个帅哥。之所以有call和apply方法实现类似的功能,就是为了方便大家使用。如果你一个一个获取参数,然后使用call,但是有时候你获取的参数是arguments,它是函数的一个内置变量,它是一个类似数组的结构,代表当前函数的所有参数,那么直接apply就可以了,不需要展开。使用bind修改thisBind是ES5引入的方法,也可以修改this,但是调用它不会立即执行方法本身,而是会返回一个修改this的新方法:constobj={myName:"大哥",func:function(age,gender){console.log(`我的名字是${this.myName},我的年龄是${age},我是${gender}`);}}constobj2={myName:"小小飞"}constfunc2=obj.func.bind(obj2);//返回一个新的方法,将this更改为obj2func2(18,"Handsomeguy");//我叫小小飞,今年18岁,帅哥bind和call,apply和call最大的区别就是apply会立即执行方法,bind不会立即执行,而是返回一个新的方法供以后使用。bind函数也可以接收多个参数,第二个及以后的参数会作为新函数的参数传入。比如之前的bind也可以这样写:constfunc3=obj.func.bind(obj2,18);//注意我们传递了一个年龄参数func3("Handsome");//注意这里只传递了性别参数,年龄参数已经在func3中了,输出依然是:我叫小飞,我的年龄是18岁,我是一个帅哥,自己写调用,知道函数的调用,让我们自己写一个调用:功能');}常量realThis=args[0]||窗户;constrealArgs=args.slice(1);constfuncSymbol=Symbol('func');realThis[funcSymbol]=这个;//这里的this是原来的方法,保存到传入的第一个参数中//使用传入的参数调用方法,方法中的this就是传入的参数constres=realThis[funcSymbol](...realArgs);删除realThis[funcSymbol];//最后删除原来的暂存方法returnres;//返回执行的返回值}自己写一个applyapply方法,和call方法很像,区别仅在于获取调用参数:Function.prototype.myApply=function(...args){if(typeofthis!=="function"){thrownewError('必须使用函数调用');}常量realThis=args[0]||窗户;//直接取第二个参数,是一个数组constrealArgs=args[1];constfuncSymbol=Symbol('func');realThis[funcSymbol]=这个;constres=realThis[funcSymbol](...realArgs);删除realThis[funcSymbol];返回资源;使用之前的apply,注意它的返回值是一个methodFunction.prototype.myBind=function(...args){if(typeofthis!=="function"){thrownewError('Mustcallwithafunction');}const_func=这个;//原始方法constrealThis=args[0]||窗户;//绑定这个constotherArgs=args.slice(1);//将以下参数作为新函数的默认参数returnfunction(...args2){//返回一个方法return_func.apply(realThis,[...otherArgs,...args2]);//拼接存储参数和新参数,然后apply执行}}总结函数外的this,即全局范围内的this指向窗口函数内的this,始终指向直接调用者。如果没有直接调用者,则隐式调用者是window。使用new来调用一个函数,这个函数就是构造函数。构造函数中的this是与实例对象通信的桥梁,它指向实例对象。箭头函数中的this在声明的时候就确定了,和当前作用域中的this是一样的。在DOM事件回调中,this指向绑定事件的对象(currentTarget),而不是触发事件的对象(target)。当然两者可以相同。如果回调是箭头函数,请参考上一篇文章,this是作用域声明时的this。在严格模式下,函数内的this指向undefined,函数外(全局作用域)的this仍然指向window。打电话申请可以改变这一点。这两个方法会立即执行原来的方法。它们的区别在于参数形式不同。bind也可以修改this,但不是立即执行它,而是返回一个修改this的函数。文末,感谢您抽出宝贵时间阅读本文。如果这篇文章给了你一点帮助或者启发,请不要吝啬你的点赞和GitHubstar。您的支持是作者继续创作的动力。欢迎关注我的公众号进取大前端第一时间获取优质原创~《前端进阶知识》系列文章源码地址:https://github.com/dennis-jiang/前端知识
