从 262 规范谈 this

988 阅读20分钟

前言

当你看到文章的标题的时候,可能会觉着这还能讲出什么花儿来。但是希望你可以在读完本篇你文章过后依旧可以有所收获。

系列文章推荐: 什么是 JS

This

There is no introduction。

This的执行环境

this 旨在提供在某个上下文环境中,来获得 当前运行环境 。我们来看一些例子:

function Fn(){
    // this = {
    //     num: 1
    // } // 类似于这样的一个对象
    this.num = 1;
}

上面代码中函数运行时,内部会自动生成一个 this 对象来使用。

那么,this 的值是什么?在不同上下文环境下被调用的 this 存在不同的值。在未读全文之前 我们暂且可以说:this 就是函数运行时所在的环境对象。

在主调用栈中执行的函数 this

function Fn(){
    console.log(this)
}
Fn() // window

// 当执行环境在严格模式下时
function Fn() { 
    'use strict'; 
    console.log(this);
}
Fn() // undefined

在 node 环境下的 this :

// 全局环境下,this指向 module.exports。
function Fn(){
    console.log(this)
}
console.log(Fn()) 

// Node 环境
console.log(this) // {}

函数实例化下的 this :

// 我们都很清楚 new 操作符执行下的函数内部 this 指向实例本身
function Fn(){
    console.log(this)
}

let fn = new Fn()

箭头函数中的 this

箭头函数的 this 会在其定义时被确定,它会抓取其所在上下文中的 this 值, 作为自己的 this,这将导致:

call() 、 apply() 、 bind() 方法对于箭头函数的 this 指向来说是无效的;且 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。

let obj = {
    info: 'A case in point.',
    print(){
        console.log(this)
        return () => {
            return this
        }
    }
}

// 函数嵌套 内部函数会 ‘丢失’ this,但作为箭头函数不会
// 会发现两次输出是一致的
console.log(obj.print()) // obj
console.log(obj.print()()) // obj

作为对象方法,以及内部嵌套函数时的 this :

当函数嵌套使用时 内部函数会 ‘丢失’ this,这将导致内外层的 this 的不一致性。

let obj = {
    info: 'A case in point.',
    print(){
        console.log(this)
        return function(){
            return this
        }
    }
}
console.log(obj.print()) // obj function(){...}
console.log(obj.print()()) // window

// 保存外部环境来保证内外部 上下文环境 一致性
let obj = {
    info: 'A case in point.',
    print(){
        const _this = this
        console.log(this)
        return function(){
            return _this
        }
    }
}
console.log(obj.print()) // obj function(){}
console.log(obj.print()()) // obj

数组中的 this :

在使用数组一系列的扩充方法中,大家可能没注意过 forEach、map 的第二个参数:

// 刚刚 我们谈到了函数嵌套内部函数会和外部函数在this指向上的两异性
// 数组在使用上同样会遇到同样的问题 解决方法一致
....
const _this = this
for(;;){
    function(){
        console.log(_this)
    }
}
....

// 我们说到了 某些数组方法存在改变 this 指向的参数
[1,2,3].map(function(){}, thisArgs)

// 我们可以在某些需要‘纠正’this的情况下,将this传入即可。

This指向的改变

this 不是被盖棺定论的。在不符合预期的时候常常通过这些方法来达到预期。

  • call、apply:修改 this 指向,传参列表不同;在使用上函数会被立即调用

  • bind:传参方式等同于 call ,返回原函数的拷贝,并拥有指定的 this 值和初始参数。这也就导致了它的执行时机:等待被调用,不会被立即执行。

// 介意手动执行
function Person() {
  this.age = 0;
  setTimeout(
    function () {
      console.log(this);
    }.call(this),
    // function () {
    //   console.log(this);
    // }.bind(this),
    3000
  );
}

const p1 = new Person(); // 当使用 call 结果会立即返回,不等待延迟
const p2 = new Person(); // 当使用 bind 结果会在delay之后返回

手写 Bind

我们只会手写 bind,它比较有意思。知其然 知其所以然,bind 到底是怎么工作的?先来看日常中怎么使用它的:

;(function(){
    const Event = function(){
          this.oBtn = document.getElementById('btn')
    }
    
    Event.prototype = {
        _init(){
            this.print()
        };
        
        print(){
            this.oBtn.addEventListener('click', this.info.bind(this), false)
        };
        
        info(){}
    }
    return new Event._init()
})()

在为 oBtn 绑定事件回调的时候,我们通过 bind 的方式将抽离后的函数进行绑定。我们也尝试过通过保存外部上下文中的 this 来保证我们使用的 this 指向的正确性:

...
print(){
    const _this = this;
    this.oBtn.addEventListener('click', function(){
        this.info.call(this)
    }, false)
};
...

在上面我们说过,bind 会返回原函数的拷贝,并拥有指定的 this 值和初始参数。

会发现我们没有用 call 代替 bind 的绑定操作,那为什么不能将 bind 替换成 call 或者 apply 来使用呢?我们说到过后两者是自执行的,也就是说他们并不会等待我们去触发事件时执行,而是会立即执行。

举个例子 🌰

我们实时监测鼠标的移动事件,如果我们使用 call 来绑定事件处理函数,我们根本无法通过处理函数来获取执行后的鼠标事件返回的任何信息。

绑定事件处理函数并不是说执行事件处理函数,执行事件处理函数的时机是:当前绑定事件处理的事件被触发的时候,这就是为什么有些情况 .call 绑定的事件执行无效。

现在,我们来对比着它的特点来实现 bind:

let per = {
    name: 'kaka'
}

function Person(){
    console.log(this)
    console.log(this.name)
}

const person = Person.bind(per)
person()

通过执行可以看到当 person() 执行时,Person 内部的会打印出预期值:{name: "kaka"} kaka

我们顺便看一下 call 是返回怎样的结果:

const person = Person.call(per) // `{name: "kaka"}` `kaka`

我们并不需要调用 person 便拿到了与使用 bind 时同样的结果,这也是符合我们预期的。

const person = Person.bind(per)
const p = new person()

我们有通过 new 的方式实例了一个 p 出来,并打印 name。 会发现 Perosn 内部的 this 指向了 Person,bind 好像失效了。那么自然而然也无法获取到 name 值了。

console.log(p.__proto__ === Person.prototype)

通过代码返回 true,可以验证 p 是 Person 的实例,也就是说 this 并没有因为 bind 的存在而使得 this 指向 per。

为什么会失效呢?因为构造函数和我们通过 bind 修改的并不是同一个 this。 就像我们在上面提到的,在构造函数被 new 的时候,函数内部会隐式的生成一个 this 对象,而 new 会将这个 this 指向实例,相当于通过 Person 来构造了实例。这也就是为什么 bind 是无效的,他们操作的本就不是同一个 this。

再如果我们要通过 call 的方式,来让 person 来生成一个实例呢?当然是会返回一个错误的,因为它并不是一个函数。

通过这几段代码我们看到了:

  1. bind 返回的是一个函数不会立即执行,函数内部的 this 指向的是修改后的 this 上下文环境。
  2. 在实例化时失效,通过 new 的方式得到的实例 this 和 bind 所修改的 this 无关,相当于通过 Person 来构造了实例。

知道了这些后,现在我们可以实现它了:

// 首先,bind 会返回一个函数,而函数也就意味着在返回的基础上依旧可以传参

Function.prototype.oBind = function (context) {// bind 在调用的时候会修改上下文,所以我们需要接收传递进来的新的上下文作为 this
  
  const _this = this,
    args = [].slice.call(arguments, 1); // 调用 bind 的同时 可能同时存在的其他参数
 
  return function () {
    const rtFnArgs = [].slice.call(arguments); // bind 返回的函数传递进来的参数
    _this.apply(context, args.concat(rtFnArgs));
  }; 
  
};

const person = Person.oBind(per);
person();

可以看到执行后的 person,返回了我们期望的数据:{name: "kaka"} kaka

function Person(info) {
  console.log(this);
  console.log(this.name);
  console.log(info);
}

person("This's a test data.")

且当调用 person 并传入参数时,同样可以按照预期执行:This's a test data.

这时, 我们做到了返回了一个函数且并不会立即执行,函数内部的 this 同时指向了新的上下文环境。

但目前为止,我们还没有通过 new 的方式来检验我们的实现成果:

// 先修改下代码
- person()

+ new person();

image.png

会发现,代码在执行时,实例 p 错误的将 this 指向了对象 per 而不是构造函数 Person。

同时为了过程清晰,再结合 bind 来看:

let person = Person.bind(per);
new person("This's a long~~~~~~~~~ message!")

image.png

由于通过 new 的方式执行的,没有 this 的输出结果是符合我们的预期的。

我们知道了目前实现的代码存在着的问题,由于 this 指向的错误从而导致了结果上的差别。那么我们该如何纠正呢?

首先我们便该想到:当调用 new 的时候,我们该怎样;如果只是 bind 又怎样。这就需要我们去判断,如果它作为一个构造函数被调用的,那么我们便应该保证 this 指向的是 Person;如果他是通过 bind 调用的,那么就说明它是一个普通函数,我们就使用当前的 context 的上下文环境就可以了。

// 修改代码
Function.prototype.oBind = function (context) {
  const _this = this,
    args = [].slice.call(arguments, 1); 
  return function () {
    const rtFnArgs = [].slice.call(arguments); 
    
-   _this.apply(context, args.concat(rtFnArgs));
+   _this.apply(this instanceof _this ? this : context, args.concat(rtFnArgs));

  };
};

this 作为通过 oBind 返回函数在被调用的指向结果:实例;_this 是调用 oBind 时的上下文环境:构造函数 Person。

现在也就意味着:如果 this(实例)是构造函数 Person 构造出来的,那么就让它指向实例即可,反之就不是通过构造函数调用的,依旧作为普通函数使用 context 即可。

来看是否有效:

image.png

恭喜大家,嗷吼! 修改无效 hhhhhhhhhhhhh 😶😐🙂😊😄😆😂🤣 !

这里是不可以这么做的,我们先看一下这里的 this 和 _this 到底是什么。

image.png

实际上是不能这么写的,可以看到 this 是一个对象,它是指向 Object 的,且我们已经在调用 oBind 的时候便已经改变了 _this 的指向了,this 就不可能再是 _this 构造出来的。再来:

Function.prototype.oBind = function (context) {
  const _this = this, 
    args = [].slice.call(arguments, 1), 
    fn = function () {
      const rtFnArgs = [].slice.call(arguments); 
      _this.apply(
        this instanceof _this ? this : context,
        args.concat(rtFnArgs)
      );
    };

  fn.prototype = this.prototype;
  return fn;
};

现在便很清楚了,我们需要做的就是将实例的原型指向构造函数的原型就好了,这时我们的实例的构造函数便指向了构造函数。

image.png

是不是觉着 fn.prototype = this.prototype; 的执行会导致指向被锁定,更离谱的还有会觉着这个同步代码会选择性执行,在 new 的时候执行,是普通函数便不去执行。

注意: 普通函数在执行这里是没有意义的,因为我只需要改变 this 指向即可。我们只调用 oBind 的时候,this 是指向 window 的,这也就达到了我们的目的:普通函数使用我们传入指定的上下文。优化:

Perosn.prototype.number = 0

Function.prototype.oBind = function (context) {
  const _this = this, 
    args = [].slice.call(arguments, 1), 
    fn = function () {
      const rtFnArgs = [].slice.call(arguments); 
      _this.apply(
        this instanceof _this ? this : context,
        args.concat(rtFnArgs)
      );
    };

  fn.prototype = this.prototype;
  console.log(fn.prototype.number = 1)
  
  return fn;
};

image.png

在优化代码之前我们先把问题抛出来,可以看到我们的实例把构造函数的原型数据改掉了!太放肆了!

Function.prototype.oBind = function (context) {
  const _this = this, 
    args = [].slice.call(arguments, 1),
    fn = function () {
      const rtFnArgs = [].slice.call(arguments);
      _this.apply(
        this instanceof _this ? this : context,
        args.concat(rtFnArgs)
      );
    };

  inherit(fn, this);
  return fn;
};

1681119679358.png

其实,当看到问题我们便很容易想到通过上边提到的圣杯模式来优化了这其中存在的缺陷。

现在整个重写便写完了,你用它呢?.

This的原理

我们在上面提到了 this 的执行环境,而导致这种差异的原因在于函数体内部使用了this关键字。 而在整个 this 的学习过程中,很多回答都是会告诉你 this 指的是函数运行时所在的环境。这种解释本没有错,但其往往不会告诉你为什么会这样?

也就是说,函数的运行环境到底是怎么决定的?就像为什么在执行栈中执行函数 this 指向 window,为什么对象中的方法指向对象本身等等。

Es5.1规范

首先在规范中找到这样一句话:The this keyword evaluates to the value of the ThisBinding of the current execution context.

这里它告诉我们的是: this 关键字的计算结果为当前执行上下文的 ThisBinding 的值。

虽然没什么有用信息,但在这里我们看到了 this 和当前执行期上下文是相关的。

ECMA262规范 6th

由于 this 与数据类型中的规范类型密切相关,所以在开始前要先知道 ECMAScript 中关于数据类型和值的相关概念,在第 6 章中:

Types are further subclassified into ECMAScript language types and specification types.【6】

The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, and Object.【6.1】

A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are ReferenceListCompletionProperty DescriptorLexical EnvironmentEnvironment Record, and Data Block. 【6.2】

内容含义就是:类型可以进一步细分为 ECMAScript 语言类型规范类型

语言类型: 有 Undefined、Null、Boolean、String、Symbol、Number 和 Object。

规范类型: 类型对应于算法中使用的 meta-values,用于描述 ECMAScript 语言结构和 ECMAScript 语言类型的语义。规范类型是 Reference、List、Completion、Property Descriptor、Lexical Environment、Environment Record 和 Data Block。

而我们要说到的 this 正和这里的规范类型紧密联系。

如何确定 this ?

我们姑且从开发层面将语言类型和规范类型称之为显性和隐性类型,知道了这些我们要如何确定 this 呢?在第 12 章中,详细的表述了不同类型表达式的规范,后面我们结合在上文中的例子来看在规范中是怎样执行的。

我们首先来看,this 关键字的运行语义:

this 被定性为初级表达式,在调用结果上返回 ResolveThisBinding() 的结果。

ResolveThisBinding():底层的抽象操作,使用正在运行中的执行上下文的 LexicalEnvironment 确定关键字 this 的绑定。 ResolveThisBinding 执行以下步骤:

  1. 使 envRec 为 GetThisEnvironment() 的调用结果;
  2. 返回 envRec.GetThisBinding()。

GetThisEnvironment():抽象操作,查找当前提供关键字 this 绑定的环境记录。 GetThisEnvironment 执行以下步骤:

  1. 让 lex 成为运行中的执行上下文的 LexicalEnvironment。

  2. 一个循环调用的并进行不同环境记录的绑定。

方法最后始终会在第 2 步中的循环中终止,因为环境列表最后会因为 this 绑定的全局环境结束。

当时我看到这里线索断了,而后我又在该方法上面发现了下面的这个方法,而这个方法让我彻底疏通了怎个 this 绑定机制。

ResolveBinding:抽象方法用于确定作为字符串传递的名称的绑定。存在可选参数提供了用于显示搜索绑定的词法环境。来看在运行中会执行该方法中的其中一个算法:

  1. 如果与正在计算的语法结果匹配的代码包含在严格模式代码中,则让 strict 为 true,否则让 strict 为 false。

而该方法的结果将始终是一个引用值:Reference,引用的名称组件等于名称参数。

Reference:

接下来我们要通过 Reference 来引出 this 是如何被确定下来的。在 6.2.3 The Reference Specification Type 中是这样描述它的:

The Reference type is used to explain the behaviour of such operators as deletetypeof, the assignment operators, the super keyword and other language features.

Reference 类型用于解释诸如 delete 、 typeof 、赋值运算符、 super 关键字和其他语言特性等运算符的行为。

Reference is a resolved name or property binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Symbol, a Number, or an Environment Record (8.1.1). A base value of undefined indicates that the Reference could not be resolved to a binding. The referenced name is a String or Symbol value.

这里说到,引用是已解析的名称或属性绑定。引用由三个部分组成:基值、引用名称和布尔值严格引用标志(上面在 ResolveBinding() 提到)。base value 可以是未定义的、对象、布尔值、字符串、符号、数字或环境记录 ( 8.1.1 )。 undefined 的基值表示引用无法解析为绑定。 引用的名称是字符串或符号值。

规范中又使用以下抽象操作来访问引用的组件,我们只提及几个与 reference 相关性强的方法:

  • GetBase(V). 返回引用 V 的 base value 组件参数值。
  • GetReferencedName(V). 返回引用 V 的 reference name 组件参数值。
  • IsStrictReference(V). 返回引用 V 的 strict reference 的标记值。

规范中使用以下抽象操作对引用进行操作,这里也只着重说如下方法:

  • GetValue. 用于从 Reference 类型获取对应返回值的方法,且该方法返回的是一个具体的值。

  • GetThisValue(V) 在 Assert:IsPropertyReference (V) 为真时,如果 IsSuperReference (V)成立则返回引用 V 的 thisValue 组件的值。最终返回 GetBase (V) 的结果。

  • WithBaseObject() 对象环境记录返回 undefined 作为它们的 WithBaseObject 除非它们的 withEnvironment 标志为真。

    • 让 envRec 成为调用该方法的对象环境记录。
    • 如果 envRec 的 withEnvironment 标志为真,则返回 envRec 的绑定对象。
    • 否则,返回 undefined。

说了这么多到底要表达什么?用一个例子来看:

var per = {
    name: "kaka"
};

// 那么相应的 Reference 应该是:
Reference = {
    base: EnvironmentRecord,
    propertyName: 'per',
    strict: false
};

这里涉及到了环境变量,简单提一下:

There are two primary kinds of Environment Record values used in this specification: declarative Environment Records and object Environment Records.

For specification purposes Environment Record values are values of the Record specification type and can be thought of as existing in a simple object-oriented hierarchy where Environment Record is an abstract class with three concrete subclasses, declarative Environment Record, object Environment Record, and global Environment Record.

大体上说:规范中使用了两种主要的环境记录值:声明性环境记录和对象环境记录。出于规范目的,Environment Record 值是 Record 规范类型的值,可以被认为存在于一个简单的面向对象的层次结构中,其中 Environment Record 是一个抽象类,具有三个具体的子类:声明性 Environment Record、对象 Environment Record 和全局 Environment Record。

截至目前可能会觉着这是到底在干什么?好了我们来进入正题:

确定 this

在整个 12 章节中,详细的描述了 Js 中所有数据的底层实现规则。在 12.3.4 提到了当函数调用的时候,如何确定 this 的取值。我们只看1、5、6,其他规则更多的是处理函数在执行上的边界。

Let ref be the result of evaluating MemberExpression.【1】

If Type (ref) is Reference, then 【5】

  1. If IsPropertyReference (ref) is true, then

    1. Let thisValue be GetThisValue.
  2. Else, the base of ref is an Environment Record

    1. Let refEnv be GetBase.
    2. Let thisValue be refEnv.WithBaseObject().

Else Type (ref) is not Reference, 【6】

a. Let thisValue be undefined.

来看下规则说了什么:

【1】 让 MemberExpression 的返回结果赋值给 ref。

【5】 判断 ref 是不是一个 Reference 类型:

  1. 如果 ref 是 Reference 类型,且 IsPropertyReference(ref) 返回结果是 true, 那么就让 this 的值为 GetThisValue(ref)

  2. 反之,则 ref 是一个 Environment Record, 那么就让 refEnv 为 GetBase (ref);让 thisValue 为 refEnv.WithBaseObject()

【6】 如果 ref 不是 Reference 类型,那么 this 的值将为 undefined。

在【1】中提到 expression 规则,我们看在函数中是怎样被定义的。

MemberExpression :

  • MemberExpression [ Expression ]
  • MemberExpression . IdentifierName
  • SuperProperty
  • MetaProperty
  • new MemberExpression Arguments

逐步分析:

// 看一个上面的例子:
let obj = {
    info: 'A case in point.',
    print(){
        console.log(this)
        return function fun(){
            return this
        }
    } 
}
// MemberExpression.IdentifierName -> obj.print
console.log(obj.print()) // obj 

// MemberExpression.IdentifierName -> obj.print()
console.log(obj.print()()) // window

上面同时又涉及到一个执行顺序上的问题,同样在 12 章有关于所有运算符号的执行规则。

根据前面的内容我们可以知道:

Reference = {
    base: obj,
    propertyName: 'print',
    strict: false
};

在 12.3.2.1 给出了 MemberExpression : MemberExpression . IdentifierName 访问器执行规则,结果会返回一个类型为 Reference 的值 (上面我们谈过),其 base valueRequireObjectCoercible (baseValue),其引用名称为 propertyNameString,其严格引用标志为 strict。

现在我们需要做的是判断 MemberExpression 的执行结果什么。通过规范我们知道该表达式返回了一个 Reference 类型值。

接下来看 5、6 的判断流程:

5.1: 如果 ref 是 Reference 类型,且 IsPropertyReference(ref) 返回结果是 true, 那么就让 this 的值为 GetThisValue(ref)

此时的 base value 是一个方法对象,那么此时我们便可以通过 GetThisValue 来确定 this 的值了。我们知道该方法最后会返回 GetBase 的值,通过 Reference 可以得知 this 便是 obj,显而易见第一个执行结果就是这里的 this 值 obj 了。

来看第二个执行,在执行上它是一个返回值并被在全局调用的函数,那么它可以是这样的:

function fun(){
    return this;
}

fun()

这就相当于 MemberExpression 是 fun,解析它的 Reference:

Reference = {
    base: EnvironmentRecord,
    propertyName: 'fun',
    strict: false
}

我们接着回忆上面的执行规则:在判断 MemberExpression 的结果上知道 fun 是一个 reference 类型值,接下来继续进行规则判断:

通过 Reference 得到的信息可以看到这里 base value 是 EnvironmentRecord,并不存在于 reference 的 base value 的限制范围,则会进入下一条判断;

在方法介绍 【5.2】中有让 refEnv 为 GetBase (ref);让 thisValue 为 refEnv.WithBaseObject(),可以得出最后的 this 指向 undefined。(规范是按照严格模式规定的)

再提一嘴 refEnv 指向 Environment Record,又通过 refEnv 调用了 WithBaseObject()。我们上面说到过 Environment 是一个抽象类型包括 global Environment Record,所以某种意义上,在非严格模式下我们依旧可以得到 this 指向 window 的结果。

虽然我们可以通过习惯来解释 this 的指向结果,但在规范的角度上同样的结果但却存在着本质上的区别。

现在我们从应用层出发,以一个容易的角度来看 this 的执行原理。 我们不再考虑底层规范是怎样规定的,从代码层来看:任何的数据值在获取时都存在有一条数据链分别指向数据上下文环境和最终的数据值。这条链我们可以称为数据结构:

数据结构:

Js 的所有 this 相关的涉及实现都依赖于内存中的数据结构关系:

const person = {
    name: "kaka"
}

代码中将一个对象和变量 person 建立了引用,Js 引擎会事先在堆中为对象分配存储空间并生成该对象,之后将该对象存储在堆中的地址放在变量的栈空间中。

也就是说,变量 person 其实是一个地址的引用。后面我们在读取的过程中便要通过 person.name 的方式,引擎会从 person 拿到对象的内存地址,再从该地址读出原始对象并返回它的属性 name 值。

数据本身都会对应一个属性描述对象,以字典的结构保存。

{
  person: {
    [[value]]: [Binary address]
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

当是一个简单的原始类型的赋值操作:let name = 'lili' 时,我们知道它应该是这样的:name - 字典信息,而属性值被保存在 value 中。

我们来看当值是一个引用类型时的情况:

const person = {
    say(){
        console.log('My name's kaka.')
    }
}
image.png

这里的属性值是一个函数。此时,在内存中的数据结构上 value 不是一个原始类型值,而是一个引用类型的地址引用。 就像上面说到的,引用类型值会开辟单独的内存空间,所以它的上下文环境会因为执行环境而变化。

由于不同变量之间的嵌套使用,加上引用类型的性质关系而导致的不同的上下文环境关系,便导致了一种机制的出现,使得能够在不同的场景下获得当前的上下文执行环境。所以指代当前运行环境的 this 便出现了。

最后 回到我们的本文核心,say 方法在 person 对象中被调用,是通过 person 找到的 say,那么 this 便指向 person;如果方法会被和全局中的某一变量建立引用,那么变量便会指向方法函数本身,所以 this 也就将指向全局。

相对于规范,这种方式在浅层次上更好理解 this 机制。

结语

本文从应用层和规范的不同角度分析了 this 的执行原理,一定程度上确实需要一定的理解强度。大家可以反复阅读,希望可以有所收获。