this关键字是JavaScript中最复杂的机制之一。它是一个特殊的关键字,自动定义在所有函数的范围内,但相信很多JsvaScript开发者都不太清楚它指向什么。.我听说你很清楚这一点,是真的吗?请先回答第一个问题:如何准确判断this指向什么?【面试常见问题】【图片来自网络,侵删】再看一个问题,控制台打印的值是多少?【浏览器运行环境】varnumber=5;varobj={number:3,fn1:(function(){varnumber;this.number*=2;number=number*2;number=3;returnfunction(){varnum=this.number;this.number*=2;console.log(num);number*=3;console.log(number);}})()}varfn1=obj.fn1;fn1.call(null);obj.fn1();console.log(window.number);如果你思考的结果和在浏览器中执行的结果是一样的,并且每一步的依据都非常清楚,那么你可以选择继续往下看,或者关闭本网页,尽情的玩吧。如果您有部分妄想,或者不确定您的答案,请继续阅读。毕竟,花一两个小时来完全理解这一点是值得的,不是吗?本文将详细讲解this的绑定规则,最后分析前两个问题。为什么要学这个?首先我们为什么要学这个?这是经常使用的。如果我们不理解这一点,就很难阅读别人的代码或源代码。工作中滥用this,但是不明白th??is指向什么,导致出现问题,但又不知道问题出在哪里。【在公司,我至少帮助过10个开发人员处理过这个问题】合理利用这一点,可以让我们写出简洁且复用性高的代码。面试中的高频问题,不好回答,不好意思,出门右转,不送货。不管目的是什么,我们都需要把这个知识弄清楚。好了,走吧!这是什么?言归正传,这是什么?首先要记住,这并不指向它自己!这是一个指向调用该函数的对象的指针。这句话我们都知道,但很多时候,我们可能无法准确判断this指的是什么?就好像我们听了很多道理,却依然过着不好的日子。今天我们不讨论如何过上好日子,但希望大家看完下面的内容后,能够一目了然。为了能够一目了然的看出this指向的是什么,我们首先要知道this的绑定规则是什么?默认绑定、隐式绑定、硬绑定、新绑定,上面这个名词,你可能听说过,也可能没听说过,但今天过后,请记住。我们依次分析。DefaultBinding默认绑定,当没有其他绑定规则可以应用时使用的默认规则,通常是一个独立的函数调用。functionsayHi(){console.log('Hello,',this.name);}varname='YvetteLau';sayHi();调用Hi()时,应用默认绑定,this指向全局对象(非严格模式),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。上面的代码如果在浏览器环境下运行,结果是Hello,YvetteLau但是如果在node环境下运行,结果是Hello,undefined。这是因为node中的名字并没有挂在全局对象上。本文中如无特殊说明,默认为浏览器环境的执行结果。隐式绑定函数的调用是在一个对象上触发的,即在调用位置有一个上下文对象。典型的形式是XXX.fun()。我们来看一段代码:person.sayHi();打印结果为Hello,外部声明了YvetteLau.sayHi函数严格来说不属于person,但是在调用sayHi时,调用位置会使用person的上下文来引用Function,隐式绑定会将this绑定在对上下文对象(即本例中的person)的函数调用(即本例中sayHi函数中的this)。需要注意的是,对象属性链中只有最后一层会影响调用位置。functionsayHi(){console.log('Hello,',this.name);}varperson2={name:'Christina',sayHi:sayHi}varperson1={name:'YvetteLau',friend:person2}person1.朋友.sayHi();结果是:你好,克里斯蒂娜。因为只有最后一层才会决定this指向什么,不管有多少层,在判断this的时候,我们只关注最后一层,也就是这里的朋友。隐式绑定有一个很大的陷阱,绑定很容易丢失(或者很容易误导我们,我们以为this指向的是什么,其实不是)。functionsayHi(){console.log('Hello,',this.name);}varperson={name:'YvetteLau',sayHi:sayHi}varname='Wiliam';varHi=person.sayHi;Hi();结果是:Hello,Wiliam。这就是为什么Well,Hi直接指向sayHi的引用。打电话时,与人无关。对于这种问题,我建议你只需要继续这样的格式:XXX.fn();fn()之前如果什么都没有,那么肯定不是隐式绑定,但也不一定是默认绑定。这里有一个小问题,后面再说。除了上面的丢失,隐式绑定的丢失还发生在回调函数中(事件回调也是其中之一),我们看下面的例子:functionsayHi(){console.log('Hello,',this.name);}varperson1={name:'YvetteLau',sayHi:function(){setTimeout(function(){console.log('Hello,',this.name);})}}varperson2={name:'Christina',sayHi:sayHi}varname='Wiliam';person1.sayHi();setTimeout(person2.sayHi,100);setTimeout(function(){person2.sayHi();},200);结果为:Hello,WiliamHello,WiliamHello,Christina第一个输出很容易理解。在setTimeout的回调函数中,这里使用的是默认绑定。在非严格模式下,执行全局对象。第二个输出有点混乱。?在讲XXX.fun()的时候,thisinfun指向的是XXX,为什么这次不是这样呢!为什么?其实我们可以这样理解:setTimeout(fn,delay){fn();},相当于把person2.sayHi赋值给一个变量,最后执行这个变量。这时候sayHi中的this显然和person2已经无所谓了。虽然第三项也在setTimeout的回调中,但是我们可以看出这是由person2.sayHi()执行的,使用了隐式绑定,所以this指向了person2,与当前作用域没有任何关系。也许你读完后有点累了,但答应我,不要放弃,好吗?再坚持一点,这个知识点就可以掌握了。显式绑定显式绑定比较容易理解,就是通过call、apply、bind显式指定this指向的对象。(注:《你不知道的Javascript》中bind单独解释为硬绑定)call、apply和bind的第一个参数是对应函数的this指向的对象。call和apply的功能是一样的,只是传递参数的方式不同。call和apply都会执行对应的函数,bind方法不会。functionsayHi(){console.log('Hello,',this.name);}varperson={name:'YvetteLau',sayHi:sayHi}varname='Wiliam';varHi=person.sayHi;嗨。打电话(人);//Hi.apply(person)的输出是:你好,YvetteLau。因为这是通过使用硬绑定明确绑定到person的。那么,使用硬绑定,是不是就意味着不会遇到隐式绑定遇到的绑定损失呢?显然不是这样,如果你不相信我,请继续阅读。functionsayHi(){console.log('Hello,',this.name);}varperson={name:'YvetteLau',sayHi:sayHi}varname='Wiliam';varHi=function(fn){fn();}Hi.call(person,person.sayHi);输出结果为Hello,Wiliam。原因很简单,Hi.call(person,person.sayHi)确实把this绑定到了Hi中的this。但是执行fn的时候,相当于直接调用了sayHi方法(记住:person.sayHi已经赋值给fn了,隐式绑定也失去了),不指定this的值,对应默认捆绑。现在,我们希望绑定不丢失,我们该怎么做呢?很简单,在调用fn的时候,也给它硬绑定。functionsayHi(){console.log('Hello,',this.name);}varperson={name:'YvetteLau',sayHi:sayHi}varname='Wiliam';varHi=function(fn){fn.call(this);}Hi.call(person,person.sayHi);此时输出结果为:你好,YvetteLau,因为person在Hi函数中绑定了this,而fn绑定了this对象给sayHi的函数。这时候sayHi中的this就指向了person对象。至此,革命已经差不多胜利了,我们再来看最后一个绑定规则:newbinding。newbindingjavaScript不同于C++,没有类,在javaScript中,构造函数只是在使用new运算符时调用的一个函数,这些函数与普通函数没有区别,它不属于某个类,也不可能实例化一个类。任何函数都可以用new调用,所以没有构造函数,只有函数的“构造调用”。使用new调用函数会自动执行以下操作:创建新对象将构造函数的作用域赋值给新对象,即this指向新对象执行构造函数中的代码返回新对象因此,我们使用new来调用函数创建时,new对象绑定到函数的this上。函数sayHi(name){this.name=name;}varHi=newsayHi('Yevtte');console.log('你好,',Hi.name);输出是Hello,Yevtte,原因是因为在varHi=newsayHi('Yevtte');在这一步中,sayHi中的this会绑定到Hi对象上。绑定优先级我们知道这个有四个绑定规则,但是如果同时应用多个规则呢?显然,我们需要知道哪种绑定方法具有更高的优先级。这四种绑定的优先级是:newbinding>explicitbinding>implicitbinding>defaultbinding这个规则怎么得来的,有兴趣的可以自己写个demo测试一下,或者记住上面的结论就可以了。绑定例外凡事都有例外,规则也是如此。如果我们将null或undefined作为this的绑定对象传入call、apply或bind,调用时这些值将被忽略,实际应用默认的绑定规则。varfoo={name:'Selina'}varname='Chirs';functionbar(){console.log(this.name);}bar.call(null);//Chirs的输出是Chirs,因为这样,实际上应用了默认的绑定规则。箭头函数箭头函数是ES6中新增的。它与普通函数有一些区别。箭头函数没有自己的this,它的this是在外层代码库中继承自this的。使用箭头函数时需要注意以下几点:(1)函数体中的this对象继承了外层代码块的this。(2)不能作为构造函数使用,即不能使用new命令,否则会抛出错误。(3)不能使用arguments对象,它在函数体中不存在。如果要使用它,可以使用rest参数代替。(4)不能使用yield命令,所以箭头函数不能作为Generator函数使用。(5)箭头函数没有自己的this,所以不能用call()、apply()、bind()等方法改变this的方向。OK,让我们看看箭头函数是什么?varobj={hi:function(){console.log(this);返回()=>{console.log(this);}},sayHi:function(){returnfunction(){console.log(this);返回()=>{console.log(this);}}},说:()=>{console.log(this);}}让嗨=obj.hi();//输出obj对象hi();//输出obj对象letsayHi=obj.sayHi();letfun1=sayHi();//输出windowfun1();//输出窗口obj.say();//输出窗口那么这是为什么呢?如果你在箭头函数中说this是定义它的对象,那么结果不是你所期望的。根据这个定义,thisinsay应该是obj。我们分析一下上面的执行结果:obj.hi();对应this的隐式绑定规则,this绑定到obj,所以obj的输出很容易理解。你好();这一步执行箭头函数。箭头函数继承了之前代码库的this。我们刚刚了解到,上一层的this是obj。显然这里是obj。执行sayHi();这一步也很容易理解。我们之前提到过隐式绑定丢失了。此时this执行默认绑定,this指向全局对象window.fun1();这一步执行箭头功能。如果按照前面的理解,this指向的是定义箭头函数的对象,那么这里显然没有意义。OK,很容易理解,箭头函数的this继承了外层代码库的this。我们刚刚分析了外层代码库,this指向window,所以这里的输出是window.obj.say();执行的是一个箭头函数,this在当前代码块obj中是不存在的,只能去网上搜索,找到了全局的this,指向window。你说箭头函数的this是静态的?还是之前的代码。让我们看看箭头函数中的this是否真的是静态的?我想说:varobj={hi:function(){console.log(this);返回()=>{console.log(this);}},sayHi:function(){returnfunction(){console.log(this);返回()=>{console.log(this);}}},说:()=>{console.log(this);}}让sayHi=obj.sayHi();让fun1=sayHi();//输出windowfun1();//输出windowletfun2=sayHi.bind(obj)();//输出objfun2();//输出obj,可以看出fun1和fun2对应的是同一个箭头函数,只是this的输出不同。因此,请记住一件事:箭头函数没有自己的this,箭头函数中的this在外部代码库中继承自this。总结一下关于这个的规则,到这里就结束了,但是如果想一眼看出来,可以看出this绑定的对象需要不断的训练。让我们回到最初的问题。1、如何准确判断this指向什么?函数是否在new(newbinding)中被调用,如果是,则this绑定到新创建的对象上。函数是通过call、apply调用,还是使用bind(即硬绑定),如果是,则this绑定到指定对象。该函数是否在上下文对象中调用(隐式绑定),如果是,则将this绑定到该上下文对象。一般obj.foo()如果以上都不是,则使用默认绑定。如果在严格模式下绑定到undefined,否则绑定到全局对象。如果将Null或undefined传入call、apply或bind作为this的绑定对象,调用时将忽略这些值,实际应用默认的绑定规则。如果是箭头函数,箭头函数的this继承了外层代码块的this。2.分析执行过程varnumber=5;varobj={number:3,fn:(function(){varnumber;this.number*=2;number=number*2;number=3;returnfunction(){varnum=this.number;this.number*=2;console.log(num);number*=3;console.log(number);}})()}varmyFun=obj.fn;myFun.call(null);obj.fn();console.log(window.number);我们来分析一下这段代码的执行过程。1、定义obj时,执行fn对应的闭包,返回里面的函数。在执行闭包中的代码时,明显不能应用newbinding(没有出现new关键字),也没有hardbinding(没有call,apply,bind关键字),有隐式绑定吗?显然不是,如果没有XX.fn(),那么可以肯定没有应用隐式绑定,所以这里应用默认绑定。在非严格模式下,this绑定到window(浏览器执行环境)。【这里很容易混淆this指向obj。注意,除非是箭头函数,否则this和词法作用域是两个不同的东西,一定要牢记]window.number*=2;//window.number值为10(varnumber定义的全局变量挂在window上)number=number*2;//number的值为NaN;注意这里我们定义了一个数,但是没有赋值,number的值为undefined;Number(undefined)->NaNnumber=3;//number的值为32.myFun.call(null);前面我们说过,call的第一个参数为null,默认调用绑定;fn:function(){varnum=this.number;this.number*=2;控制台日志(数量);数字*=3;console.log(number);}执行时:varnum=this.number;//数量=10;此时this指向windowthis.number*=2;//window.number=20console.log(num);//输出结果为10number*=3;//数字=9;包裹中的数量;闭包中的数字是3console.log(number);//输出结果为93.obj.fn();应用了隐式绑定,fn中的this对应obj.varnum=this.number;//num=3;此时this指向objthis.number*=2;//obj.number=6;console.log(num);//输出为3;number*=3;//数字=27;与此编号对应的闭包中的编号;thenumberintheclosureis9console.log(number);//输出结果为274最后一步console.log(window.number);输出结果为20,所以组内结果为:10932720严格模式下的结果,可以按照这个每天学到的东西,自己分析,巩固知识点。最后,恭喜坚持阅读的小伙伴们。你已经成功获得了这方面的知识点,但是如果你想完全掌握它,你还需要多复习和多练习。如果你对此有很好的锻炼,欢迎在评论区留言,我们一起进步!感谢您花宝贵的时间阅读本文。如果这篇文章给了你一点帮助或者启发,那就不要吝啬你的点赞和star。您的肯定是我前进的最大动力。https://github.com/YvetteLau/...感谢指出,添加参考链接如下:《你不知道的JavaScript书籍》ES6文档-箭头函数(http://es6.ruanyifeng.com/#do...经典面试题文章,有链接可以给我留言,推荐关注我:
