理解JavaScript的核心知识点:This

Understanding JavaScript Core: This

thisJavaScript 中很是重要且使用最广的一个关键字,它的值指向了一个对象的引用。这个引用的结果很是容易引发开发者的误判,因此必须对这个关键字刨根问底。javascript

执行上下文:Execution Context

在深刻了解 this 对象以前先介绍另外一个概念:执行上下文。html

没错,执行上下文与 this 在本质上是两个概念,或者说它们指代的范畴有差别,想要准确认识 this,就得先把它们区分开。java

能够把执行上下文想象为一个容器,其中包含了一句句待执行的代码。代码在这个容器中有上下行两条路线,是由某一些特殊代码所触发(如函数),上行路线跳入了一个新的容器,开始在新容器中执行另外一些代码,本容器中的后续代码被暂时中断;若是新容器中还有代码会触发上行路线,就继续往上增长新容器,并交出控制权,层层叠加,造成了一个从底往上形式的叠罗汉,这就是 JavaScript 运行时的执行上下文栈。git

执行上下文这一抽象概念自己包含了更多有关 JavaScript 这门语言的内部机制,对于语言使用者来讲是不透明的,其中与运行前的编译规则有很大关联,并被包含到整个程序运行前的初始化过程当中,与词法做用域的变量解析规则相配合,将这些静态解析后的变量带入运行时的环境,因此它是程序运行时的关键内部组件或者说容器,而 JavaScript 将对执行上下文的引用提供给程序开发者的惟一入口就是 this,它得以访问被编译后带入到某个执行上下文运行环境中的变量。this 指代的其实只是内部抽象的执行上下文向用户所开放的那一部分,其实体是一个对象,绑定了许多编译后的变量。程序员

如下是一段关于执行上下文精辟的总结:github

An execution context is purely a specification mechanism and need not correspond to any particular artefact of an ECMAScript implementation. It is impossible for ECMAScript code to directly access or observe an execution context.web

翻译:执行上下文纯粹是一种规范机制,它不须要与基于 ECMAScript 规范的任何特定扩展实现对应。ECMAScript 代码没法直接访问或观察执行上下文。编程

关于This对象:What's This

我将官方文档和一些别的文章里的说明稍加梳理,能够从如下段落中较为清晰地看出 this 的本质:app

First, know that all functions in JavaScript have properties, just as objects have properties. And when a function executes, it gets the this property—a variable with the value of the object that invokes the function where this is used.编程语言

The this keyword evaluates to the value of the ThisBinding of the current execution context.

The abstract operation GetThisEnvironment finds the Environment Record that currently supplies the binding of the keyword this

this is not assigned a value until an object invokes the function where this is defined.

翻译:

  • 首先,要知道 JavaScript 中全部的函数与对象同样都拥有属性。当一个函数执行时,它获得 this 属性——一个指向调用函数的对象的变量。
  • this 关键字计算为当前执行上下文的 ThisBinding 属性的值。
  • GetThisEnvironment 抽象运算查找当前提供 this 关键字的绑定的环境记录。
  • 在对象调用了定义了 this 的函数以前,this 不会被赋值。

由此可得出关于 this 的彻底定义:this 是在程序运行时,经过语言内部抽象操做在执行上下文中动态计算获得的,指向调用使用了其的函数的对象的变量。

执行上下文 vs. This关键字:Execution Context vs. This Keyword

执行上下文和 this 关键字的关系与潜意识相对于意识的关系相似,执行上下文是冰山下深邃庞大而不可窥探的秘地,而 this 只将其一个小部分显露出来。因为 JavaScript 是面向对象的编程语言,因此执行上下文其实质至关于一个对象,this 指向了它向开发者开放了的一系列属性集合的对象,于是我把 this 叫作执行上下文的引用对象。

This因何而来:Why This

JavaScript 在编写初始借鉴了JAVAC 语言的特性,即使本质上不一样,但仍是把这个如同惯例般存在的 this 拿了过来。使用 this 的缘由其实很简单:

首先,咱们时常没法得知调用了函数的对象的名称,而且有时候根本就没有名称能够用来引用调用对象。这是一个迫切的缘由,由于咱们在开发时一定会遇到须要引用调用函数的对象的场景。

其次,避免重复指代,就像咱们常用第三人称来指代前文的主体同样,做为程序员你们固然很乐意使用一个快捷方式来避免机械重复一些没必要要的代码,这也是“语言”这一重要产品的特性。

最后,它提供给咱们实现高级功能的可能性,咱们能够经过 this 动态对于执行上下文的指代而实现程序的复用性和扩展。

This的判断规则:Rules of This

this 的根源进行深刻探究的目的就是为了在开发中对本身所使用的 this 关键字指代的对象进行准确的断定,它就是一个变量,因此当咱们使用它的时候,必须清晰地知道它的值究竟是什么。

通常来讲,咱们能够经过肯定是哪一个对象拥有所调用的函数来肯定其 this 的指向。这是因为 this 的绑定值是在函数调用的时候才赋予的,要看函数在哪一个上下文对象中调用,但有时候这不是仅用肉眼就能观察出来的。

此外还要严肃声明一下,虽然在以前下定义的时候将 this 的概念明确地划分到了运行阶段,但因为它做为一个变量的特性,是能够改变引用值的,它的值的计算与词法规则仍是息息相关,得将编译和运行时两个阶段结合起来,总结出关于判断 this 绑定值的基本原则。

this 关键字绑定的操做是在语言内核机制的运行时里执行的,因为没法去探索其内部,只能经过官方文档中给出的一系列描述程序来得知其如何判断,能够梳理出函数调用的内部过程当中对 this 的绑定计算的依据:

前置知识 1: 内部机制建立执行上下文、初始化函数所属领域和建立相关环境记录

在函数被真正执行以前,内部机制会执行建立拥有函数的领域、建立执行上下文、移交当前执行上下文控制权、建立环境记录、环境记录对象参数的绑定等一系列操做,为程序运行作编译准备。在将函数推入执行栈顶层的时候,对其上下文的归属有如下的判断过程,此处与一个新的概念领域有关:

  • 若是领域中的属性 this 返回了一个对象,就将内部属性 thisValue 设置为以此对象为基础按照规格建立的 js 对象,不然 thisValue 绑定值为 undefined,代表领域的全局对象(本地全局对象)将设置为全局对象(程序全局对象)。

这里在新规范里出现的一个概念领域取代了以前版本中简单的做用域的概念,因为实现了模块化等其余新特性,因此做用域的概念能够至关于扩展成了如今的领域,它下属了其余几个环境记录,其中变量的绑定分别在不一样环境记录中,这里就不作深刻解释了。

领域中比较重要的属性是领域中的全局对象,这与程序运行时的全局对象的概念要加以区别,因此能够把领域中的全局对象看做是本地全局变量,其实也就是函数所属的上下文对象,它的值就是在刚才的以上的判断中肯定的,若是没有这个前置对象,就会把全局对象设置为本地全局对象的值。

前置知识 2: 内部机制建立函数

内部机制在词法分析阶段会经过函数的定义方式向建立函数操做传入几种不一样类型的函数类型:NormalArrowMethod,相对应的是普通函数、箭头函数、做为对象方法的函数。同时在这一步还传入指定代码严格模式的参数 strict。而后进行函数的初始化的。

方式 1: 内部机制初始化普通函数

内部机制在这一步会设置函数的一个重要属性 ThisMode 的值,它是决定 this 绑定值的依据,它的值是根据上一步传入的参数来判断的,依次执行一下三条判断分支:

  • 函数类型为 Arrow:将 ThisMode 赋值为 lexical ,这个值在计算 this 绑定时将按照词法做用域的规则来赋值,也就是说 this 的值与定义函数的词法做用域中的 this 相一致。
  • 代码模式为 strict :将 ThisMode 赋值 strict,按照这个值计算 this 绑定时只会将显式传入的上下文对象绑定给 this
  • 非以上两种条件:将 ThisMode 赋值 global,被设置为 global 以后,函数在运行阶段被调用时,this 的值就会指向全局对象。

方式 2: 内部机制建立对象方法函数

做为对象属性的方法是另外来计算 this 的,只有在做为对象方法被调用的函数,在内部建立函数时才会传入 Method 值。毫无疑问它将 this 指向了这个前置的对象。构造函数也是同理。

总结一下对通常使用到的函数的判断规则以下:

  • 箭头函数:不管调用位置,取它词法定义处的外层上下文中绑定的 this,没有中间本地对象存在时老是可以取到全局对象。
  • 严格模式:不管调用位置,只取显式给定的上下文绑定的 this,经过 call()apply()bind() 方法传入的第一参数,不然是 undefined
  • new 关键字调用的构造器函数:不管调用位置,this 必为在内部建立的新的实例对象
  • 显式绑定上下文对象的普通函数:不管调用位置,this 必为传入的上下文对象
  • 方法函数:属于隐式绑定,不管词法定义位置,实际状况视调用处而定:
    • 直接调用时:this 为前置上下文对象
    • 做为被引用值时:this 为调用时的上下文对象,在其余对象中引用 this 就是这个调用它的对象;被全局变量引用,this 就是全局对象。
  • 普通函数:不管词法定义位置,视调用处而定,其实质在内存都都是被做为引用值调用的,因此 this 都指向全局对象,严格模式规则优先。

另外关于事件形成的一些 this 误解能够参考The this keyword这篇文章。其实并不属于特殊规则,是因为各类事件监听定义方式自己形成的。

在实际开发中能够参考《You Don't Know JS》里关于 this 的绑定规则和优先级的章节Nothing But Rules。在这套基础通用规则以外,箭头函数利用了另外一套方式来判断 this 的绑定值,这篇文章里也有详尽的叙述。

参考文献:Reference