JS中的this是个老生常谈的问题,因为它不是一个确定的值,而且在不同的情况下它有不同的方向,所以经常让人迷惑。本文将谈谈我自己对此的理解。这到底是什么?其实这是一个指针,它表示当前的执行环境,可以用来对当前的执行环境进行一些操作。因为它表示执行环境,所以在定义这个变量时,它的真实值是未知的,只能在运行时确定它的值。同一段代码,不同的执行方式,其thispoint可能不同。我们来看看下面的代码:functionfunc(){this.name="小小飞";console.log(this);//看看这是什么}这个方法很简单,就是给this加一个name属性,我们把这个方法复制到Chrome调试工具中看看结果:我们在上图中直接调用了func(),发现this指向的是window,在window中添加了name属性。接下来我们改一下调用方式,我们使用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);//这里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总是指向调用者。让我们稍微改变一下这个例子:varmyName="BigBrother";varobj={myName:"LittleFei",func:function(){console.log(this.myName);}}varanotherFunc=obj.func;anotherFunc();//输出是什么?这里的输出应该是“大哥”,因为anotherFunc的函数体虽然和obj.func一样,但是执行环境不同。其实它并没有明确的调用者,或者调用者是window。这里的this.myName其实就是window.myName,也就是“飞哥”。我们再改一下这个例子:anotherFunc();//注意这里的输出是undefined这次我们只是把第一个var改成let,但是我们的输出已经变成undefined了。这是因为let和const定义的变量,即使在最外层,也不会成为window的属性,只有var定义的变量才会成为window的属性。箭头函数不绑定this这句话的意思是箭头函数本身是没有this的。当箭头函数声明为确定this时,此时,它会直接使用当前作用域的this作为自己的this。在前面的例子中,我们把函数改成了箭头函数:varmyName="BigBrother";varobj={myName:"LittleFei",func:()=>{console.log(this.myName);}}varanotherFunc=obj.func;obj.func();//大哥anotherFunc();//上面代码中obj.func()的输出也是“大哥”,因为obj在创建的时候声明了箭头函数,此时箭头函数会搜索当前作用域,因为obj是对象,不是作用域,所以这里的作用域是window,this也是window。再看一个例子:varmyName="大哥";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,因为调用的是Obj,所以this指向obj。那为什么anotherFunc()的输出也是“小小飞”呢?这是因为anotherFunc()的this输出实际上是在分配anotherFunc时确定的:varanotherFunc=obj.func().getName;其实obj.func()是先执行的,getName箭头是在obj.func()执行时执行的,在函数声明时,箭头函数的this应该是当前作用域的this,即func()中的thisfunc()是obj调用的,所以调用anotherFunc时this指向obj。是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指向事件绑定的对象/如果target等于currentTarget,则为true}constele=document.getElementById('test');ele.addEventListener('click',func);currentTarget指的是绑定事件的DOM对象,target指的是触发事件的对象。在DOM事件回调中,this总是指向currentTarget。如果触发事件的对象刚好是绑定事件的对象,即target===currentTarget,this也会顺便指向target。如果回调是箭头函数,则this是声明箭头函数时作用域的this。在严格模式下,this是undefinedfunctionfunc(){"usestrict"console.log(this);}func();//输出是undefined注意严格模式下this是undefined的意思是在函数体内,如果在函数体全局作用域,this仍然指向window。......这个可以改吗?这个是可以改的,call和apply都可以修改这个,ES6还增加了bind函数。使用call和apply修改thisconstobj={myName:"大哥",func:function(age,gender){console.log(`我的名字是${this.myName},我的年龄是${age},我是a${gender}`);}}constobj2={myName:"小小飞"}obj.func.call(obj2,18,"帅哥");//我叫小小飞,我的年龄是18岁,我amahandsomeguy注意上面输出的名字是“小小飞”,也就是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修改这个bind是ES5引入的方法,也可以修改this,但是调用它不会立即执行这个方法本身,而是会返回一个修改this的新方法:constobj={myName:"BigBrother",func:function(age,gender){console.log(`我的名字是${this.myName},我的年龄是${age},我是${gender}`);}}constobj2={myName:"XiaoXiaofei"}constfunc2=obj.func.bind(obj2);//返回一个新的方法,这个改成了obj2func2(18,"Handsome");//我叫XiaoXiaofei,我的年龄是18岁,我bind和call,apply最大的区别就是call,apply会立即执行方法,而bind不会立即执行,而是返回一个新的方法供以后使用。bind函数也可以接收多个参数,第二个及以后的参数会作为新函数的参数传入。比如前面的bind也可以这样写:constfunc3=obj.func.bind(obj2,18);//注意我们传递了一个年龄参数func3("Handsome");//注意只有性别参数这里传递的,age参数已经在func3中了,输出依然是:我叫小飞,我的年龄是18岁,我是一个帅哥,自己写了一个调用,知道调用的功能。我们自己写一个调用:Function.prototype.myCall=function(...args){//参数检查if(typeofthis!=="function"){thrownewError('Mustcallwithafunction');}constrealThis=args[0]||window;constrealArgs=args.slice(1);constfuncSymbol=Symbol('func');realThis[funcSymbol]=this;//这是原来的Method,保存到传入的第一个参数中//使用传入的参数in调用方法,方法中的this是传入的参数constres=realThis[funcSymbol](...realArgs);deleterealThis[funcSymbol];//最后删除原先的暂存方法returnres;//返回returnvalueoftheexecution}自己写一个applyapply方法,和call方法很像,区别仅在于调用参数:Function.prototype.myApply=function(...args){if(typeofthis!=="function"){thrownewError('Mustcallwithafunction');}constrealThis=args[0]||window;//直接取第二个参数,为数组constrealArgs=args[1];constfuncSymbol=Symbol('func');realThis[funcSymbol]=this;constres=realThis[funcSymbol](...realArgs);删除erealThis[funcSymbol];returnres;}自己写一个bind。需要用之前的apply写一个bind。注意它的返回值是一个方法Function.prototype.myBind=function(...args){if(typeofthis!=="function"){thrownewError('Mustcallwithafunction');}const_func=this;//原方法constrealThis=args[0]||window;//绑定这个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。您的支持是作者继续创作的动力。
