🚀🚀🚀原来this的判定这么简单

132 阅读12分钟

引言

你是不是在判断this指向的时候,第一反应就是默认绑定、隐式绑定、显示绑定、new绑定等,其中还要区分各种情况,比如隐式绑定丢失等情况。传统的理解方式(默认绑定、隐式绑定、显示绑定、new绑定)虽然有效,但往往需要记忆很多规则和例外情况,那么有没有一种统一的方法论来快速的判定this指向呢?答案是:必须有啊!

如何快速判定this指向

关于如何判定this的指向,其实这个方法就藏在ECMAScript文档中,好了不卖关子了,直接揭晓答案,那就是Reference类型。如果你对这个类型很陌生,那么你就慢慢的看下去,如果你了解这种判定方法,那也可以温故而知新

坏笑.jpeg

ECMAScript 规范

ECMAScript 6 语言规范

ECMAScript 规范中定义了两大类类型:

  1. Language Type(语言类型)

    • 我们在开发中直接使用的类型 包括:number、string、boolean、object、function、undefined、null
  2. Specification Type(规范类型)

    • 用于描述 ECMAScript 语言结构和语言类型的底层实现,包括:Reference、List、Completion、Property Descriptor、Property Identifier、Lexical Environment、Environment Record
    • 这些类型在代码中不可见,仅用于描述语言底层的行为逻辑

Reference 类型

Reference

Reference 是 ECMAScript 规范中定义的规范类型(Specification Type),用于描述变量、属性或方法调用的底层引用关系。它直接影响:

  • this 的绑定
  • 赋值操作
  • delete 操作

Reference 的组成结构

组成部分描述示例(obj.fn)
base引用的基础对象(对象、环境记录或 undefined)base = obj
referencedName引用的名称(字符串或 Symbol)referencedName = "fn"
strict布尔值,表示是否在严格模式下strict = false
thisValue用于确定函数调用时的 this 值
thisValue 如何生成?​

根据 ​​Reference 的组成结构​​({ base, name, strict }),thisValue 的生成规则如下:

情况thisValue 的值示例
base 是对象​base 本身obj.fn() → this = obj
base 是环境记录(变量)​undefined(严格模式)或 windowfn() → this = window

注意: thisValue 是引擎内部的“this 计算器”​​,根据 Reference 的 base 快速确定 this。可以理解为它是base值映射成this绑定的结果,可以简单的将thisvalue值看成和base值的等价关系,故之后介绍只介绍base,referencedName、strict.

举个例子:

var a = 10
var obj = {
  a: 20,
  say(){
    console.log(this.a)  
  }  
}
obj.say() 

obj.say()的Reference 结构为

 {
     base: obj,                    // 基础对象
     referencedName: 'say',      // 引用的属性名
     strict: false                 // 非严格模式
   }

代码输出结果为20,因为base为对象,thisValue等于base,所以obj.say()执行时的this就是obj,所以输出结果就是20.

Reference 类型生成规则?
  1. 计算 MemberExpression(如 obj.fn)得到 ref
  2. 判断 ref 类型:
    • 如果是 Reference:
      • IsPropertyReference(ref) = true ⇒ this = GetBase(ref)
      • base 是环境记录 ⇒ this = ImplicitThisValue(ref)
    • 如果不是 Reference ⇒ this = undefined/window

什么是 MemberExpression? 在ECMAScript规范 13.3 Left-Hand-Side Expressions:最后的解释为: MemberExpression :

  • PrimaryExpression // 原始表达式
  • FunctionExpression // 函数定义表达式
  • MemberExpression [ Expression ] // 属性访问表达式
  • MemberExpression . IdentifierName // 属性访问表达式
  • new MemberExpression Arguments // 对象创建表达式
function test() {
    console.log(this)
}

test(); // MemberExpression 是 test

function test() {
    return function() {
        console.logthis)
    }
}

test()(); // MemberExpression 是 test()

var test = {
    play: function () {
        return this;
    }
}

test.play(); // MemberExpression 是 test.play

总结一下 MemberExpression 就是()左边的部分,

实例分析:

var obj = {
  name: 'obj',
  fn: function() {
    console.log(this);
  }
};
obj.fn(); // this = obj

执行过程:
1. 计算 obj.fn,得到 ref(Reference 类型,base = obj)。
2. IsPropertyReference(ref)为 true(因为obj是对象)。
3. this = GetBase(ref)= obj。

var baz = obj.fn;
baz(); // this = undefined(严格模式)或 window(非严格模式)
执行过程:
1.  baz 是一个变量,计算baz得到的是 函数本身(非 Reference 类型)。
2.  由于ref不是 Reference 类型,this = undefined(严格模式)或 window(非严格模式


(obj.fn)(); // this = obj
执行过程:

1.  (obj.fn) 仍然是 obj.fn,ref 是 Reference 类型,base = obj。
2.  IsPropertyReference(ref) 为truethis = obj。

(baz = obj.fn)(); // this = undefined(严格模式)或 window(非严格模式)
执行过程:
1.  baz = obj.fn 是一个赋值表达式,返回的是函数本身(非 Reference 类型)。
2.  ref不是 Reference 类型,this = undefined(严格模式)或 window(非严格模式)。

总结

情况MemberExpression 计算ref 类型IsPropertyReferencethis 值
obj.fn()obj.fnReferencetrueobj
baz()baz(变量)​非 Reference​-undefined/window
(obj.fn)()obj.fnReferencetrueobj
(baz = obj.fn)()赋值表达式返回函数​非 Reference​-

注意: Reference 判断规则只适用于普通函数,对于NEW和箭头函数等函数底层属性本来就有改变this属性的,上述说的MemberExpression 就是()左边的部分不适用

实战案例

光说不练假把式,趁着手热,找几道题试试

案例1

var obj = { 
  name: 'wsw', 
  fun: function(){ 
     console.log(this.name); 
  } 
} 
obj.fun()     // wsw

ref = {
  base: obj,           // 基础对象是 obj
  referencedName: "fun", // 引用的属性名是 "fun"
  strict: false        // 非严格模式
}
操作Reference 视角结果
obj.fun{ base: obj, referencedName: "fun" }生成 Reference
IsPropertyReferencebase 是对象 obj → truethis = obj
obj.fun()执行 fun 时 this = obj输出 "wsw"

案例2

var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
 
obj.method(fn, 1);
输出结果: 10 2

执行步骤​

​1:fn() fn 的 Reference 类型​

  • fn 是直接作为参数传递的 ​​函数本身​​,不是属性访问,因此:
ref_fn = fn // 非 Reference 类型(直接函数值)
  • this 绑定规则​​:
    • 非严格模式:this = window(全局对象)。
    • 严格模式:this = undefined
    • 输出:this.length → window.length → 10(全局变量)。

​2:arguments[0](): arguments[0] 的 Reference 类型​

arguments[0] 是 fn,但通过 arguments 对象访问,其 Reference 结构为:

ref_arguments = {
    base: arguments, // arguments 对象
    referencedName: "0", // 属性名 "0"
    strict: false
}
  • this 绑定规则​​:
    • IsPropertyReference(ref_arguments) 为 truebase 是 arguments 对象)。
    • this = GetBase(ref_arguments) → arguments
    • ​输出​​:this.length → arguments.length → 2obj.method(fn, 1) 传入了 2 个参数)。

​3. 关键点总结​

调用方式Reference 类型this 绑定输出结果
fn()​非 Reference​​(直接函数值)window(非严格模式)10
arguments[0](){ base: arguments, name: "0" }arguments2

​4. 为什么 arguments[0]() 的 this 是 arguments?​

arguments[0] 是 ​​属性访问​​(类似 obj.fn),因此生成 Reference 类型:

{
    base: arguments, // arguments 对象
    referencedName: "0",
    strict: false
}

根据规则:

  • IsPropertyReference(ref) 为 true → this = GetBase(ref) → arguments
  • arguments.length 是传入的参数个数(这里是 2)。

案例3

var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}

obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1

执行步骤​

​1:obj.foo()

  • obj.foo 的 Reference 类型​​:

    ref_foo = {
      base: obj,           // 基础对象是 obj
      referencedName: "foo", // 属性名是 "foo"
      strict: false        // 非严格模式
    }
    
    
  • this 绑定规则​​:

    • IsPropertyReference(ref_foo) 为 truebase 是对象 obj)。

    • this = GetBase(ref_foo) → obj

    • ​输出​​:this.a → obj.a → 2


​2:obj.bar()

  • obj.bar 的 Reference 类型​​:

    ref_bar = {
      base: obj,
      referencedName: "bar",
      strict: false
    }
    
  • ​执行 bar()​:

    • this 绑定到 obj(规则同 obj.foo())。

    • 但 bar 内部的 printA() 是 ​​直接调用​​,其 Reference 类型:

     ref_printA = printA // 非 Reference 类型(直接函数值)
    
  • this 绑定​​:

    • 非严格模式:this = window(全局对象)。
    • 严格模式:this = undefined
    • ​输出​​:this.a → window.a → 1(全局变量)。

​3:foo()

  • foo 的赋值​​:
    • var foo = obj.foofoo 直接引用 printA 函数。

    • foo 是 ​​独立变量​​,没有 base 对象,因此:

    ref_foo = {
      base: Global Environment Record, // 全局环境记录
      referencedName: "foo",
      strict: false
    }
  • ​执行 foo()​:
    • foo 是全局变量,this 由 ImplicitThisValue(ref_foo) 决定:
      • 非严格模式:this = window
      • 严格模式:this = undefined
    • ​输出​​:this.a → window.a → 1(全局变量)。

​3. 关键点总结​

调用方式Reference 类型this 绑定输出结果
obj.foo(){ base: obj, name: "foo" }obj2
obj.bar()bar 的 this 是 obj,但内部的 printA() 是直接调用window1
foo(){ base: 全局环境记录 }window1

​4. 为什么 obj.bar() 输出 1?​

  • bar 虽然是 obj 的方法,但 ​bar 内部的 printA() 是独立调用​​:

    • printA() 没有通过对象调用(如 obj.printA()),因此 this 绑定到全局对象。
  • ​对比​​:

    • obj.foo() 是 ​​方法调用​​ → this = obj
    • printA() 是 ​​函数调用​​ → this = window

​5. 最终输出​

obj.foo(); // 2(this = obj)
obj.bar(); // 1(this = window)
foo();     // 1(this = window)

​6. 总结​

  • ​方法调用(obj.fn())​​:this 绑定到 obj(Reference 的 base 是对象)。
  • ​函数调用(fn())​​:this 绑定到全局对象(非严格模式)。
  • ​赋值后调用(var x = obj.fn; x())​​:this 丢失原始绑定,变为全局对象。

案例4

function foo(something){
    this.a = something
}

var obj1 = {
    foo: foo
}

var obj2 = {}

obj1.foo(2); 
console.log(obj1.a); // 2

obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4
  1. obj1.foo 的 Reference 类型​

    ref = {
        base: obj1,
        referencedName: "foo",
        strict: false
    }
    
  • this 绑定规则​
    • IsPropertyReference(ref) 为 true → this = GetBase(ref) → obj1

    • 执行 foo(2)​:this.a = 2 → obj1.a = 2

    • ​输出​​: console.log(obj1.a) → 2


  1. obj1.foo.call(obj2, 3) 执行过程

    • call 的作用​​:强制将 foo 的 this 绑定到 obj2

    • ​执行 foo.call(obj2, 3)​:this = obj2 → obj2.a = 3

    • ​输出​​:console.log(obj2.a) → 3


  1. new obj1.foo(4)(new 绑定)​
  • new 的作用​​:创建一个新对象 bar,并将 foo 的 this 绑定到 bar
  • ​执行 new foo(4)​:
    • this = bar(新创建的对象)→ bar.a = 4
    • obj1.a ​​未被修改​​(仍然是 2)。
  • ​输出​​:
    • console.log(obj1.a) → 2(未变)。
    • console.log(bar.a) → 4

调用方式this 绑定规则this 的值修改的对象输出结果
obj1.foo(2)方法调用obj1obj1.a = 2obj1.a = 2
foo.call(obj2, 3)callobj2obj2.a = 3obj2.a = 3
new obj1.foo(4)new 绑定barbar.a = 4bar.a = 4

案例5

var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x = 5;
        return function() {
            return this.x;
        }();
    },
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

执行步骤​

  1. ​调用 obj.getX()

    • obj.getX 的 Reference 类型​​:

      ref_getX = {
          base: obj,
          referencedName: "getX",
          strict: false
      }
      
    • ​执行 getX()​:

      • this 绑定到 obj(方法调用)。

      • 在 getX 内部:

        • 定义局部变量 x = 5(不影响全局或对象属性)。

        • 返回一个 ​​立即执行的匿名函数​​:

          function() {
              return this.x; // this 绑定规则?
          }()
          
    • ​匿名函数的 this​:

      • 匿名函数是 ​​直接调用​​(不是方法调用),因此:

        • 非严格模式:this = window(全局对象)。
        • 严格模式:this = undefined
      • this.x → window.x → 3(全局变量 x)。

    • ​输出​​:console.log(obj.getX()) → 3


  1. ​ 调用 obj.getY()

    • obj.getY 的 Reference 类型​​:

      ref_getY = {
          base: obj,
          referencedName: "getY",
          strict: false
      }
      
    • ​执行 getY()​:

      • this 绑定到 obj(方法调用)。

      • 在 getY 内部:

        • 定义局部变量 y = 7(不影响全局或对象属性)。
        • this.y → obj.y → 6
    • ​输出​​: console.log(obj.getY()) → 6


  1. ​ 关键点总结​
调用方式this 绑定规则this 的值访问的 x 或 y输出结果
obj.getX()匿名函数是直接调用windowwindow.x(全局)3
obj.getY()方法调用objobj.y6

​4. 为什么 obj.getX() 输出 3?​

  • 匿名函数 function() { return this.x; }() 是 ​​独立调用​​,this 绑定到全局对象。
  • 全局 x = 3,因此 this.x 返回 3
  • ​注意​​:局部变量 x = 5 不影响结果,因为它不是通过 this 访问的。

​5. 为什么 obj.getY() 输出 6?​

  • getY 是方法调用,this 绑定到 obj
  • obj.y = 6,因此 this.y 返回 6
  • ​注意​​:局部变量 y = 7 不影响结果,因为它不是通过 this 访问的。

案例6

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {
            console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();

分步解析​

​1:myObject.func() 的 Reference 分析​

  • ​计算 myObject.func​:myObject.func 是属性访问,生成 Reference 类型:

     ```js
     ref_func = {
         base: myObject,       // 基础对象是 myObject
         referencedName: "func", // 引用的属性名
         strict: false        // 非严格模式
     }
     ```
    
  • this 绑定​​:

    • IsPropertyReference(ref_func) 为 truebase 是对象 myObject)。
    • this = GetBase(ref_func) → myObject

​2:执行 func 函数​

  • var self = this​:this 是 myObject → self 也指向 myObject

  • ​输出 this.foo 和 self.foo​:

    • console.log(this.foo):this 是 myObject → myObject.foo → "bar"
    • console.log(self.foo):self 是 myObject → myObject.foo → "bar"

​3:立即执行函数的 Reference 分析​

  • ​立即执行函数的 this​:

    • 匿名函数是 ​​直接调用​​((function() { ... }())),没有通过对象调用。

    • 其 Reference 类型:

      ref_anon = 匿名函数本身 // 非 Reference 类型(直接函数值)
      
    • this 绑定​​:

      • 非严格模式:this = window
      • 严格模式:this = undefined
    • console.log(this.foo)this 是 window → window.foo → undefined

  • ​立即执行函数的 self​:

    • self 是外层函数 func 的变量,通过闭包保留了对 myObject 的引用。
    • console.log(self.foo)self 是 myObject → myObject.foo → "bar"

​4. 关键点总结​

代码位置Reference 类型this 绑定输出结果
myObject.func(){ base: myObject, name: "func" }myObject"bar"
console.log(this.foo)-myObject"bar"
console.log(self.foo)-myObject(通过闭包)"bar"
立即执行函数 this.foo非 Reference(直接函数值)windowundefined
立即执行函数 self.foo-myObject(通过闭包)"bar"

​5. 为什么立即执行函数的 this 是 window?​

  • 立即执行函数是 ​​独立调用​​,其 Reference 类型是 ​​非 Reference​​(直接函数值)。

  • 根据规则:

    • 非严格模式:this 默认绑定到全局对象(window)。
    • 严格模式:this = undefined
  • window.foo 未定义,因此输出 undefined


​6. 为什么 self 仍然指向 myObject?​

  • self 是外层函数 func 的局部变量,通过闭包被内部函数引用。
  • 闭包保留了 self 的值(myObject),因此 self.foo 始终访问 myObject.foo

案例7

var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

分步解析​

​1:调用o()

  • o 的赋值​​:

    • var o = obj.sayo 直接引用 obj.say 的函数。

    • o 是一个 ​​独立变量​​,没有 base 对象,因此:

      ref_o = {
        base: Global Environment Record, // 全局环境记录
        referencedName: "o",
        strict: false
      }
      
  • ​执行 o()​:

    • o() 是 ​​独立函数调用​​,this 由 ImplicitThisValue(ref_o) 决定:

      • 非严格模式:this = window
      • 严格模式:this = undefined
    • say 内部的箭头函数 f1 继承 say 的 this(即 window)。

  • ​输出​​:console.log("1111", this) 输出 1111 window


​2: 调用obj.say()

  • ​计算 obj.say 的 Reference​​:

    ref_say = {
      base: obj,
      referencedName: "say",
      strict: false
    }
    
  • this 绑定​​:

    • IsPropertyReference(ref_say) 为 truebase 是对象 obj)。
    • this = GetBase(ref_say) → obj
  • ​执行 say()​:箭头函数 f1 继承 say 的 this(即 obj)。

  • ​输出​​:console.log("1111", this) 输出 1111 obj


​3:调用obj.pro.getPro()

  • ​计算 obj.pro.getPro 的 Reference​​:

    ref_getPro = {
      base: obj.pro, // pro 是对象
      referencedName: "getPro",
      strict: false
    }
    
  • ​箭头函数的 this 绑定​​:

    • getPro 是箭头函数,this 由词法作用域决定。
    • getPro 定义在 pro 对象中,但 pro 不是函数作用域,因此 this 继承外层全局作用域的 this(即 window)。
  • ​输出​​: console.log(this) 输出 window


​4. 关键点总结​

调用方式函数类型this 绑定规则this 的值输出结果
o()普通函数独立调用 ⇒ this = windowwindow1111 window
obj.say()普通函数方法调用 ⇒ this = objobj1111 obj
obj.pro.getPro()箭头函数词法作用域 ⇒ this = windowwindowwindow

​5. 为什么 obj.pro.getPro() 的 this 是 window?​

  • 箭头函数 getPro 的 this ​​在定义时确定​​,而 pro 对象只是一个普通对象,​​不是函数作用域​​。
  • 因此 getPro 的 this 继承自全局作用域(window),与 obj.pro 无关。

​Reference 类型与函数 this 绑定的终极总结​

1. Reference 类型的核心作用​

  • ​概念​​:ECMAScript 规范中的抽象类型,描述变量/属性/方法的引用关系。

  • ​结构​​:{ base, referencedName, strict }

    • base:基础对象(对象、环境记录或 undefined
    • referencedName:引用的名称(字符串或 Symbol)
    • strict:是否严格模式

​2. 普通函数的 this 绑定规则​

调用方式Reference 类型this 绑定规则示例
​方法调用​​ obj.fn(){ base: obj, name: "fn" }this = GetBase(ref) → objobj.fn() → this=obj
​独立调用​​ fn(){ base: 环境记录 }this = window(非严格模式)let x=obj.fn; x() → this=window
​call/apply/bind​-显式指定 thisfn.call(obj) → this=obj
​new 调用​-this = 新创建的对象new Fn() → this=newObj

​3. 箭头函数的特殊性​

  • ​规则​​:this ​​继承定义时的外层作用域​​,完全无视 Reference 规则

  • ​示例分析​​:

    const obj = {
      fn: () => console.log(this) // this 继承全局作用域(window)
    }
    obj.fn(); // 输出 window(而非 obj)
    

​4. 匿名函数与自执行函数​

类型this 绑定规则典型场景示例输出
​匿名函数​同普通函数(取决于调用方式)作为回调函数时可能丢失 thissetTimeout(function(){ console.log(this) }, 100) → window
​自执行函数​默认绑定到 window立即执行的独立作用域(function(){ console.log(this) })() → window
​闭包中的匿名函数​通过变量保留外层 this解决回调函数 this 丢失问题let self=this; function(){ console.log(self) }

​5. 经典对比案例​

const obj = {
  a: 1,
  normalFn: function() { console.log(this.a) },
  arrowFn: () => console.log(this.a)
};

// 场景1:直接调用
obj.normalFn(); // 1(Reference.base=obj)
obj.arrowFn();  // undefined(继承全局)

// 场景2:间接调用
const fn1 = obj.normalFn;
const fn2 = obj.arrowFn;
fn1(); // undefined(base=环境记录)
fn2(); // undefined(箭头函数已固化this)