JavaScript 中 this 的指向规则:从代码实践到原理解析

97 阅读4分钟

JavaScript 中 this 的指向规则:从代码实践到原理解析

在 JavaScript 中,this是一个特殊的关键字,其指向一直是前端开发者绕不开的难点。与作用域链(由编译阶段决定)不同,this的指向由函数的调用方式在运行阶段动态决定。本文将结合具体代码案例,详细解析this在不同场景下的指向规则。

一、this 与自由变量:本质区别

在讨论this之前,需要先明确它与 “自由变量” 的核心差异:

  • 自由变量:指在函数内部使用、但既不是函数参数也不是局部变量的变量,其查找依赖词法作用域链(由函数声明时的位置决定,属于编译阶段确定的静态规则)。
  • this:其指向不依赖声明位置,而是由函数被调用时的方式决定(属于运行阶段的动态规则)。

这一差异使得this成为 JavaScript 中灵活但也容易混淆的特性。

二、this 的指向规则与代码实践

1. 普通函数调用:指向全局对象(严格模式下为 undefined)

当函数以普通方式(非对象方法、非构造函数等)调用时,this默认指向全局对象(浏览器环境中为window)。在严格模式('use strict')下,thisundefined,以避免全局变量污染。

代码示例

<script>
    var myObj = {
        name:'极客时间',
        showThis:function(){
            this.name='极客邦'  // this指向由调用方式决定
            console.log(this);
        }
    }
    var foo = myObj.showThis;  // 将函数引用赋值给全局变量foo
    foo();  // 普通函数调用:this指向window
</script>

解析

  • myObj.showThis是对象的方法,但当我们将其赋值给全局变量foo后,foo()的调用属于普通函数调用
  • 此时this指向全局对象window,因此this.name = '极客邦'实际修改的是window.nameconsole.log(this)输出window对象。

image.png

2. 作为对象方法调用:指向调用对象

当函数作为对象的方法被调用时,this指向该对象本身,这是面向对象编程中访问对象属性的核心方式。

代码示例

<script>
var myObj = {
    name:'极客时间',
    showThis:function(){
        console.log(this);  // this指向调用对象myObj
    }
}

myObj.showThis();  // 作为对象方法调用
</script>

解析

  • myObj.showThis()中,函数showThismyObj调用,因此this指向myObjconsole.log(this)输出myObj对象(包含name: '极客时间')。

image.png

3. 使用 call/apply:强制绑定 this

callapply方法允许手动指定函数执行时的this指向,第一个参数即为this的目标指向。

代码示例

<script>
let bar = {
    myName:'极客邦',
    test1:1
}
function foo() {
    this.myName='极客时间'  // this指向被call/apply指定的对象
}

// foo.call(bar);  // 与apply效果一致,参数传递方式不同
foo.apply(bar);
console.log(bar);  // 输出 {myName: '极客时间', test1: 1}
</script>

解析

  • foo.apply(bar)强制将foo函数的this指向bar对象,因此this.myName = '极客时间'实际修改了barmyName属性。
  • 最终bar对象的myName'极客邦'变为'极客时间'

image.png

4. 构造函数调用:指向实例对象

当函数通过new关键字作为构造函数调用时,this指向新创建的实例对象。

代码示例

<script>
function CreateObj() {
    console.log(this);
    this.name='极客时间'
}
var myObj = new CreateObj();
</script>

解析

  • new操作会创建一个新对象,将构造函数的this指向该对象,并执行构造函数内的代码(如this.name = '极客时间'会给实例添加name属性)。

5. 事件处理函数:指向绑定事件的元素

在 DOM 事件处理中,事件处理函数的this指向触发事件的 DOM 元素(即事件绑定的元素)。

代码示例

<a href="#" id="link">点击我</a>
<script>
document.getElementById('link').addEventListener("click",function(){
    console.log(this);  // this指向<a>元素
})
</script>

解析

  • 当点击<a>标签时,事件处理函数被触发,此时this指向该<a>元素,console.log(this)会输出该 DOM 节点的信息。 image.png

6. 综合对比:作用域与 this 的区别

代码示例

<script>
  'use strict';
  var bar = { 
    myName: "time.geekbang.com",
    printName: function() {
      console.log(myName);  // 自由变量,查找全局作用域的myName
      console.log(bar.myName);  // 直接访问对象属性
      console.log(this);  // 由调用方式决定
      console.log(this.myName);  // 访问this指向的对象的myName
    }
  }

  function foo() {
    let myName = '极客时间'  // 局部变量,不影响全局
    return bar.printName
  }

  var myName='极客邦';  // 全局变量
  var _printName = foo();
  _printName();  // 普通调用:this指向window(严格模式下为undefined)
  bar.printName();  // 对象方法调用:this指向bar
</script>

解析

  • printName中的myName是自由变量,从全局作用域查找,值为'极客邦'
  • _printName()是普通函数调用,严格模式下thisundefined,因此this.myName会报错。
  • bar.printName()this指向bar,因此this.myName'time.geekbang.com'

三、总结

JavaScript 中this的指向完全由函数的调用方式决定,核心规则可归纳为:

调用方式this 指向
普通函数调用全局对象(严格模式下为 undefined)
作为对象方法调用调用该方法的对象
使用 call/apply 调用手动指定的第一个参数
构造函数(new)调用新创建的实例对象
DOM 事件处理函数绑定事件的 DOM 元素

理解this的关键在于明确函数 “如何被调用”,而非 “在哪里被声明”。通过多实践不同场景下的代码,可以更直观地掌握this的动态指向特性。