本文部分摘自《你不知道的JavaScript(上)》
深入浅出理解JavaScript中的this指向
全文将从以下四个方面阐述JS中this的指向问题且全文将按照“声明概念->给出代码->解释说明”的方式阐述this指向问题。希望本文能够帮助您更深一步地理解this指向问题。
- 默认绑定
- 隐式绑定
- 显式绑定
- new 操作符
1.默认绑定
在讨论这个问题之前,我们先要搞明白两个概念:调用位置和调用栈。
调用位置:从字面意思来看,就是函数被调用的位置,事实上也就是如此,只不过需要注意好声明位置和调用位置不是一回事即可。
调用栈:我们大可将它理解为“为了到达当前执行位置所调用的所有函数。”
弄明白上述概念后,我们即可来说什么叫默认绑定,默认绑定即为:在调用函数时,使用任何不带任何修饰的函数引用进行调用的,都会触发默认绑定,即将this 指向 window。这句话理解起来似乎是那么抽象,简而言之就是当你直接使用函数名称调用时,就会触发默认绑定。
下面以代码为例解释:
在这里需要强调的是,尽管foo是baz调用的,那么为什么foo打印的this还是指向window呢?根据上面我们所说的,只要是使用函数名称直接调用的,甭管怎么样,this就是指向window.
此外我们需要注意ES5的严格模式。
当函数运行在严格模式下,this将不再指向window,而是指向undefined.
仅仅当函数运行在严格模式下,this的默认绑定会受影响,在严格模式中调用函数并不会影响this的指向。
2.隐式绑定
何为隐式绑定,即当调用函数时不是直接使用函数名称直接调用且当前有执行期上下文对象的,将会触发隐式绑定,this将指向这个执行期上下文对象。
下面以代码为例进行解释:
无论对象里面的函数是直接在Obj里面定义的还是在外面定义而后在Obj里面引用,严格来说,这个函数都不属于Obj对象。
这样调用会使用Obj的上下文来引用函数,因此this指向Obj的上下文,此时的this.a == obj.a。
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到 这个上下文对象身上。
2.1链式调用
值得注意的是,假如出现了链式调用( obj1.obj2.obj3.foo() ),那么foo的this将指向obj1还是obj2亦还是obj3。我们不妨写一个例子来测试一下:
由此得知,当出现链式调用时,只有最后一层或上一层的上下文对象才是this绑定的。
2.2隐式丢失
所谓隐式丢失从字面意思上来看就是偷偷的丢了。那么到底是什么意思呢?隐式丢失指的是在运行过程中,因为一些隐式的操作,影响了this的指向,本来我们想将this指向A,因为一些隐式操作,this却指向了B,这就叫作隐式丢失。
我们来看下面一个小例子:
按照常理foo将打印1,但实际上却打印了 window.scope,为什么会发生这种情况?我们来分析一下。首先定义了一个函数foo,接着定义了一个obj,然后在全局声明了一个变量a,接着把obj.foo的引用赋给了 bar,引用?对,就是这里出现了问题。当我们把obj.foo的引用赋给bar时,此时bar是函数foo的引用,当我们使用bar()进行调用时,这里是直接使用函数名称进行调用的,因此会触发默认绑定,即this将指向window。因此我们打印this.a实际上是打印了window.a。
明白了这个,下面解释在函数回调过程中也会发生隐式丢失就不困难了,来看下面一段代码:
3.显式绑定
显式绑定就是我们需要了解的两个方法:
- call
- apply
如果我们需要显式的指定this的指向,我们使用这两个方法就行了,这里不再对call和apply展开叙述,只给出一个小例子,后续会再写一篇针对call和apply的文章。
尽管call和apply能够显示指定this的指向,但是仍然无法解决我们刚才遇到的隐式丢失问题,在此我们着重讨论一下硬绑定(解决隐式丢失问题)
硬绑定从名字来看我们就知道什么意思了,把this死死的绑定到我们需要绑定的地方上。
硬绑定总共有4种方法,我们只捡其中的2种来讨论,让我们来看下面代码:
至于为什么不直接使用 foo.call/foo.apply 方法,反而又去定义一个bar去调用,我仍在思考中,如果您对此有好的理解并且您愿意分享的话,劳烦您在评论区留下您的宝贵理解!
4.new 构造函数
在JS中实际上不存在构造函数,只存在对于函数的“构造调用”,并无什么构造函数,只不过是通过new调用了一个普通函数而已,我们来讨论一下在 new 调用的过程中,与this有关的是什么。
到此,本篇关于this的理解到此结束,文章或有些许不足,但还是希望能够帮助到您。谢谢!