this关键字是JavaScript函数内部的对象,this是指向调用该函数的对象的指针。看似简单的定义,但是由于解析这个引用的过程中可能涉及到执行上下文、作用域链、闭包等复杂的机制,使得这个指向的问题变得极其复杂。首先,我们必须明白,任何复杂的机制都不是容易学习和理解的。因此,本文将和大家一起耐心回顾一下this对象,总结一下this的引用机制。希望对您有所帮助。1.功能是否执行?要了解this对象,首先要了解什么时候执行函数?让我们看一个简单的示例(示例1):functionfn(){console.log('Hello');}fn;fn();letf=fn;f();上面的例子一共输出了2次Hello。fn()和f()表达式中的函数被调用并执行,但fn和letf=fn表达式中的函数不被调用和执行。函数的执行主要看是否有函数名(),使用不带括号的函数名会访问函数指针而不是调用函数我们来看一个添加闭包机制的例子(例2):functionfn(){lethi='Hello'返回函数gn(){console.log(hi);}}fn;fn();让f=fn;f();让g=fn();g();很复杂,要输出多少次hello?hello的输出必须在函数gn被调用的时候执行,所以关键是函数gn是什么时候调用的?根据前面的例子,如果表达式fn,f=fn没有调用函数fn,也就不会调用函数gn。根据前面的例子,表达式fn()和f()含义相同,都调用了函数fn。在闭包中,调用fn返回一个函数指针,该函数指针返回一个函数gn,但是最终并没有通过函数指针调用gn,所以在表达式fn(),f(),g=中并没有执行函数gnfn()。表达式g=fn()可以将函数gn赋值给g,最终通过g()完成函数gn的调用和执行。类似于:lethi='Hello';让g=function(){console.log(hi);}g();//函数执行所以最后这个例子只输出了一次hello。最后,看一个对象内部函数调用的例子(例3):日志(嗨);}}}o.fn;o.fn();让f=o.fn;f();让g=o.fn();g();在这个例子中,你输出了多少次??实际上,无论函数定义在对象内部还是对象外部,都可以通过前面例子的分析步骤来分析函数被调用执行的过程。因此,在本例中,只输出了一个hello。在全局环境中定义的函数,该函数自动成为window对象的一个??方法,即全局环境中的fn()调用等同于window.fn()调用。2.神奇的thiswhat,上面说了很多,这个还没说?别着急,要理解this的引用机制,我觉得最重要的是理解函数执行的上下文。如果你连函数什么时候执行都搞不清楚,就更不可能理解这个对象了。来吧,让我们继续探索。红皮书第四版对this对象的描述为:1.在标准函数中,this指的是将函数视为方法调用的上下文对象2.在箭头函数中,this指的是箭头所在的上下文函数定义(1)标准函数的this对象普通函数(箭头函数除外)内部都有一个this对象。this确定执行函数时引用的对象。这里有两个关键点:执行时间和对象。普通函数只有在执行时才能确定这一点。那代码可以在定义的时候确定吗?不!记住:当函数执行时OK!函数执行时OK!函数执行时OK!普通函数的this指向调用该函数的对象。那可以指向其他功能吗?它可以指向原始数据类型吗?一点也不!记住:指向调用函数的对象指向调用函数的对象指向调用函数的对象两个关键点。来吧,我们通过例子进一步分析,下面将跟随掘金文章:嗨,你真的了解这个吗?讨论了分类标准。默认绑定默认绑定简单的说就是函数在全局环境中执行,即没有对象直接调用函数。在这种情况下,函数的this会指向window(非严格模式)或undefined(严格模式)//在非严格模式下,this指向windowfunctionfn1(){console.log(this);}//在严格模式下,this是undefinedfunctionfn2(){'usestrict'console.log(this);}简单,但是你能准确判断出函数是在全局环境下执行的吗?请看下面的例子(例4):varhi='window'leto={hi:'object',gn:function(){lethi='function';控制台日志(this.hi);},fn:function(){lethi='函数'returnfunction(){lethi='闭包函数';控制台日志(this.hi);};}}o.gn();letf=o.fn();f();一旦涉及到对象内部方法、闭包等机制,问题就会复杂很多。你能看到总共输出了多少次吗?在全局环境中执行了多少次输出?根据上一章的分析,我们可以知道有两个输出(不明白可以回到第一节),分别是要输出的表达式o.gn()和f()对象,窗口。分析如下:表达式o.gn()显然是通过对象o调用了函数gn,所以执行gn时,this指向o;表达式letf=o.fn();会执行fn函数并将闭包函数的函数指针赋值给f变量,此时执行的函数是fn而不是闭包函数,所以此时fn的this指向对象o,但是闭包函数的this尚未确定;通过调用表达式f();让闭包函数执行。此时闭包函数没有被对象调用执行,而是运行在全局环境中,所以闭包函数的this会指向window(非严格模式)。所以不管函数怎么赋值,只要函数不执行,this指针就不会确定指向的对象。第一个重点是了解函数什么时候执行!第二个关键点,就是要搞清楚这个函数是怎么调用的!隐式绑定隐式绑定是指当通过对象调用函数时,函数的this指向对象。简单的说,谁调用函数,谁就是函数是谁。我们来看下面这个例子,来自知乎文章:JavaScript中this的原理是什么?(示例5)consto1={text:'o1',fn:function(){returnthis.text}}consto2={text:'o2',fn:function(){returno1.fn()}}consto3={text:'o3',fn:function(){varfn=o1.fnreturnfn()}}console.log(o1.fn())console.log(o2.fn())控制台。log(o3.fn())乍一看很复杂,但本质上还是需要找到是哪个对象调用了函数来执行。分析一下:当表达式console.log(o1.fn())执行时,对象o1调用了执行函数fn,因此函数fn的this指向对象o1,所以输出o1;当表达式console.log(o2.fn())被执行时,对象o2调用了执行函数fn,因此,o2的内部函数fn的this指向了对象o2,但是等一下,这里有一个表达式returno1.fn(),不难看出this通过o1调用了o1的内部函数fn(函数this指向o1的对象),返回执行结果。于是绕道回去执行o1的内部函数fn,输出o1;当表达式console.log(o3.fn())被执行时,对象o3调用执行函数fn,因此o3的内部函数fn的this指向对象o3,但是,o3的内部函数fn并没有直接使用this,而是通过赋值操作获取到o1内部的fn函数,并执行fn函数。请注意,这里有一个陷阱。最终fn函数是在没有对象调用的情况下执行的,所以fn函数的this指向了window。这个和Example4类似,不明白的可以回头看看Example4,一定要区分默认绑定和隐式绑定的场景。关键是要判断函数的执行时间,然后找出是哪个对象调用了函数。下面结合回调函数来看另一个例子(例6):varhi='window'leto={hi:'object',fn:function(){lethi='function';setInterval(function(){console.log(this.hi);},1000);}}o.fn();你认为应该输出什么?我们先来分析一波:显然,在表达式o.fn();的执行过程中,函数fn的this必须指向对象o;查看fn函数,执行了setInterval函数,特别是传入了匿名函数作为回调函数,匿名函数在每一秒的执行过程中没有任何对象可以调用,所以匿名函数的this指向窗口,最后输出窗口。结合传参看另一个例子(例7):varhi='window'leto={hi:'object',fn:function(){lethi='function';控制台日志(这个);}}functiongn(fn){fn();}gn(o.fn)你认为这次的输出是什么?持续分析:首先明确一点:参数的传递等同于赋值。所以gn(o.fn)等价于f=o.fn;gn(f);好家伙,又是assignment,不执行函数的都是骗子!函数gn内部执行的是传入的函数fn,并没有发生对象调用,所以此时的执行环境是全局环境,窗口输出。赋值、回调和关闭是它的头号敌人。在找到关联对象之前,您必须等待函数执行。显示绑定显示绑定是指通过call、apply、bind重定向函数的this,直接指定函数this指向的对象。varhi='window'leto={hi:'object',}functionfn(){lethi='function';控制台日志(this.hi);}fn.call(o);通过fn函数的call方法可以强制在全局环境下执行的fn函数内部的this指向对象o,所以输出:object。我们来思考一下方法调用、apply、bind的区别?红皮书第四版是这样解释的:call和apply的功能相同,只是输入参数的形式不同。call传递给函数的参数需要一一列出,而apply需要使用参数数组来传入参数。bind方法将创建一个新的函数实例,其this值绑定到传递给bind的对象。值得注意的是,call和apply方法调用后会直接执行函数,而bind方法不会,但bind方法会一直将固定的this绑定到新创建的实例上。bind的具体用法如下:varhi='window'leto={hi:'object',}functionfn(){lethi='function';控制台日志(this.hi);}令f=fn。bind(o);f();//无论f如何调用,f里面的this始终指向对象o,output:objectfn();//this仍然按照正常的绑定规则绑定,output:windownewbinding在使用构造函数创建特定类型的对象时会出现new关键字,看红皮书对new关键字操作的解释:红皮书第四版对new操作步骤的解释为:在内存中创建一个新对象。新对象的内部[[Prototype]]特征被分配给构造函数的原型属性。构造函数里面的this被赋值给这个新对象(也就是this指向新对象)。执行构造函数内部的代码(给新对象添加属性)如果构造函数返回非空对象,则返回该对象;否则,返回新创建的新对象。上面的运行过程已经很清楚了,看下面的例子(例8):functionfn(){this.name="Tony";this.showName=function(){console.log(this.name);}}让newObj=newfn();newObj.showName();结合红皮书中的解释,我们可以知道,有以下步骤:生成一个新的匿名对象。匿名对象的[[Prototype]]特性被赋值为构造函数的原型属性(这部分涉及到原型链的知识)。构造函数fn内部的this指向匿名函数执行fn的内部代码。匿名函数添加属性名和方法showName返回匿名函数赋值给newObj(2)箭头函数的this对象相对于普通函数多了一个this对象,箭头内部没有this对象功能。你没听错,箭头函数里面没有this对象!那么如何判断箭头函数的this引用呢?回顾JavaScript的作用域链机制,当函数的作用域中不存在某个变量时,它会在作用域链中向后查找,直到找到某个变量或者因为找不到而报错。因此,如果箭头函数内部没有this对象,那么在使用this对象时,必须找到外层函数的this对象或者窗口的this对象,箭头函数对应的外层this关系是在使用时确定的箭头函数是定义好的,所以无论在哪里调用箭头函数,箭头函数能找到的this在定义的时候就已经确定了。我们用一个例子来求箭头函数的this(例9):varhi='window'leto={hi:'object',gn:()=>{lethi='arrowfunction';控制台日志(这个.hi);},fn:function(){lethi='函数'return()=>{lethi='箭头函数';控制台日志(this.hi);};}}o.gn();让f=o.fn();f();f=o.fn.call(窗口);f();先找箭头函数的this:函数gn是一个箭头函数,外层没有其他函数包,所以根据变量解析的作用域链规则,箭头函数的this就是this的窗口。函数fn是一个返回箭头函数的匿名函数。根据作用域链规则,在查找箭头函数this的过程中,查找到的外层函数fn的this就是箭头函数的this。最后得到:gn箭头函数的this就是窗口的this;fn返回的箭头函数的this就是函数fn的this。运行上面的例子,可以得到如下浏览器的输出,简单分析一下:第一个输出是由表达式o.gn()生成的,由于gn箭头函数的this就是window的this,所以hi变量是窗口;第二个输出由表达式生成letf=o.fn(); f();生成。由于fn返回的箭头函数的this就是函数fn的this,函数fn的this通过表达式o.fn()指向对象o,导致箭头函数的this也是o,最终输出对象;第三个输出由表达式f=o.fn.call(window)产生;f();,因为fn返回的箭头函数的this就是函数fn的this,通过表达式f=o.fn.call(window),将函数fn的this指向window,这样箭头函数的this也是窗口,最后是输出窗口。最后请大家思考一个问题,箭头函数的this点是否可以通过call()、apply()、bind()等方法直接改变?3.综上所述,this对象是JavaScript的一个比较复杂的知识点。看过一些文章多分类讨论this对象引用问题或者直接给出公式,混合作用域链,闭包,赋值,回调,参数传递等等,一个知识点搞的太复杂看不懂。在我看来,这个对象的设计其实很精妙。重点是在函数执行的时候抓住this的本质,然后研究几个特殊场景下的例子,更好的理解这个对象的指向问题。最后你会发现普通函数和箭头函数本质上是一样的。唯一的区别是普通函数有自己的this,而箭头函数没有自己的this。由于作者水平有限,如有不妥还望指正。感谢参考资料:JavaScript高级程序设计(第四版)掘金篇:嗨,你真的看懂了吗?知乎文章:JavaScript中this的原理是什么?
