JavaScript 中的 this:谁在调用我?


在 JavaScript 中,this 是一个非常特殊的关键字,它不像普通变量那样固定不变,而是 "见风使舵"------谁调用函数,this 就指向谁 。理解 this 是掌握 JavaScript 的关键一步,因为它直接影响代码的执行结果。

一、什么是 this?

this 可以理解为函数的 "上下文",也就是函数在执行时所处的环境。它的作用是隐式传递对象引用,让代码更简洁。比如:

javascript 复制代码
const user = {
  name: "张三",
  sayHi() {
    console.log(`你好,我是${this.name}`); // 这里的this就代表user对象
  }
};
user.sayHi(); // 输出:你好,我是张三

如果没有 this,我们可能需要写成 console.log(user.name),当对象名称变化时,所有引用都要修改,而 this 帮我们避免了这种麻烦。

二、不同场景下的 this 指向

this 的指向不是在定义函数时决定的,而是在函数被调用时 决定的。不同的调用方式,this 会指向不同的对象。

1. 全局作用域中的 this

在全局作用域(不在任何函数内),this 直接指向全局对象:

  • 浏览器环境中,全局对象是 window

  • Node.js 环境中,全局对象是 global

javascript 复制代码
console.log(this === window); // 浏览器中输出 true
console.log(this); // 直接打印全局对象(包含大量内置属性和方法)

2. 函数作用域中的 this

函数中的 this 指向调用该函数的对象,具体分四种绑定规则:

规则一:默认绑定(独立函数调用)

当函数独立调用(没有被任何对象 "点" 调用)时,this 指向全局对象。

javascript 复制代码
function a() {
  console.log(this);
}

a(); // 独立调用,this指向window(浏览器环境)

注意 :在严格模式("use strict")下,默认绑定的 this 会变成 undefined,而不是全局对象。

规则二:隐式绑定(对象调用函数)

当函数被某个对象调用(即通过 对象.函数() 的形式)时,this 指向这个调用函数的对象

javascript 复制代码
    const obj = {
      name: "李四",
      sayName() {
        console.log(this.name); // this指向obj
      }
    };

    obj.sayName(); // 输出:李四(因为是obj调用了sayName)
javascript 复制代码
     var a = 1
     function foo() {
       console.log(this.a) // 2
     }
     var obj = {
        a:2,
        foo:foo
     }
     obj.foo() //函数foo被obj调用,this指向obj

如果函数被多层对象嵌套调用,this 指向最近的那个调用对象(离函数最近的 "点" 前面的对象):

javascript 复制代码
    const obj1 = {
      a: 1,
      obj2: {
        a: 2,
        showA() {
          console.log(this.a); // this指向obj2(最近的调用对象)
        }
      }
    };

    obj1.obj2.showA(); // 输出:2
css 复制代码
    var a = 1
    function foo() {
       console.log(this.a); //obj.a
    }

    var obj = {
      a:2,
      foo:foo
    }

    var obj2 = {
      a:3,
      obj:obj
    }

    obj2.obj.foo()

规则三:显式绑定(强制修改 this 指向)

我们可以通过 callapplybind 这三个方法,主动指定函数的 this 指向,不管函数原本该指向谁。

这三个方法的作用类似,但用法有区别:

方法 语法 特点
call 函数.call(指向的对象, 参数1, 参数2...) 直接调用函数,参数逐个传入
apply 函数.apply(指向的对象, [参数1, 参数2...]) 直接调用函数,参数以数组形式传入
bind const 新函数 = 函数.bind(指向的对象, 参数1, 参数2...) 不立即调用,返回一个绑定好 this 的新函数
javascript 复制代码
function introduce(age, hobby) {
  console.log(`我是${this.name},年龄${age},爱好${hobby}`);
}

const person = { name: "王五" };

// call用法
introduce.call(person, 20, "打篮球"); // 输出:我是王五,年龄20,爱好打篮球

// apply用法(参数用数组)
introduce.apply(person, [20, "打篮球"]); // 输出同上

// bind用法(返回新函数,需要手动调用)
const boundFunc = introduce.bind(person, 20, "打篮球");
boundFunc(); // 输出同上

规则四:new 绑定(构造函数调用)

当用 new 关键字调用函数(此时函数作为构造函数)时,this 指向新创建的对象

javascript 复制代码
    function Person(name) {
      this.name = name; // this指向new创建的新对象
    }

    const p1 = new Person("赵六");
    console.log(p1.name); // 输出:赵六(this将name赋值给了p1)
javascript 复制代码
    const obj = {
      name: '111',
      show:function()  {
        console.log(this.name); 
      }
    };
    obj.show(); // 111
dart 复制代码
    const obj = {
      name: '111',
      show: () => {
        console.log(this.name); // 不是111,而是外层this
      }
    };
    obj.show();

new 关键字的执行过程(结合 this 理解):

  1. 创建一个空对象({}
  2. 让构造函数的 this 指向这个空对象
  3. 执行构造函数代码(给空对象添加属性,空对象的隐式原型 __ proto__ ===构造函数的显示原型prototype)
  4. 返回这个新对象

规则优先级

如果同时满足多个规则,优先级顺序为:
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定(对象调用) > 默认绑定(独立调用)

3. 箭头函数中的 this(特殊情况)

箭头函数是个例外 ------它没有自己的 this ,它的 this 继承自外层作用域的 this,且一旦确定就不会改变。

javascript 复制代码
const obj = {
  name: "箭头函数测试",
  // 普通函数:this指向obj
  normalFunc() {
    console.log(this.name); 
  },
  // 箭头函数:this继承外层作用域(这里外层是全局)
  arrowFunc: () => {
    console.log(this.name); 
  }
};

obj.normalFunc(); // 输出:箭头函数测试(this指向obj)
obj.arrowFunc();  // 输出:undefined(this指向window,而window.name可能未定义)

常见坑点 :在对象方法中使用箭头函数,可能导致 this 指向不符合预期(如上例)。箭头函数更适合在嵌套函数中使用,解决 this 丢失问题:

javascript 复制代码
const timerObj = {
  name: "计时器",
  start() {
    // 普通函数作为定时器回调,this会指向window
    setTimeout(function() {
      console.log(this.name); // 输出:undefined
    }, 1000);

    // 箭头函数作为回调,this继承自start的this(即timerObj)
    setTimeout(() => {
      console.log(this.name); // 输出:计时器
    }, 1000);
  }
};

timerObj.start();

三、特殊场景中的 this

1. DOM 事件处理函数

在 DOM 事件回调中,this 指向触发事件的元素

xml 复制代码
<button id="btn">点击我</button>
<script>
  const btn = document.getElementById("btn");
  btn.onclick = function() {
    console.log(this); // 指向按钮元素(<button>)
    this.style.color = "red"; // 可以直接操作当前元素
  };
</script>

2. 定时器 / 延时器回调

setTimeout的回调函数中,this 默认指向全局对象(window):

javascript 复制代码
const obj = { name: "定时器" };

setTimeout(function() {
  console.log(this); // 指向window,而非obj
  console.log(this.name); // 输出:undefined
}, 1000);

解决办法:用箭头函数继承外层 this,或用 bind 绑定:

javascript 复制代码
// 方法1:箭头函数
setTimeout(() => {
  console.log(this); // 继承外层this(如果外层this是obj的话)
}, 1000);

// 方法2:bind绑定
setTimeout(function() {
  console.log(this.name);
}.bind(obj), 1000); // 输出:定时器

3. 类中的 this

  • 类的普通方法中,this 指向实例对象

  • 类的静态方法(用 static 修饰)中,this 指向类本身

javascript 复制代码
    class Student {
      constructor(name) {
        this.name = name; // 普通方法(构造函数),this指向实例
      }

      sayName() {
        console.log(this.name); // this指向实例
      }

      static showClass() {
        console.log(this); // 静态方法,this指向Student类本身
      }
    }

    const stu = new Student("小明");
    stu.sayName(); // 输出:小明
    Student.showClass(); // 输出:class Student { ... }

四、避免 this 丢失的实用技巧

this 丢失是开发中常见问题(比如将对象方法赋值给变量后调用),解决办法:

  1. bind 提前绑定 this

    go 复制代码
    const obj = { 
        name: "绑定测试", 
        getName() { 
            return this.name;
        }
    };
    const func = obj.getName.bind(obj); // 绑定this为obj
    console.log(func()); // 输出:绑定测试
  2. 用箭头函数继承外层 this

    javascript 复制代码
    const obj = { 
      name: "箭头绑定",
      getFunc() {
        return () => { 
            return this.name;
        }; // 箭头函数继承getFunc的this(即obj)
      }
    };
    const func = obj.getFunc();
    console.log(func()); // 输出:箭头绑定

总结

理解 this 的核心是记住:谁调用函数,this 就指向谁。具体场景可归纳为:

  • 普通函数独立调用:this 指向全局对象(严格模式为 undefined
  • 对象调用函数:this 指向该对象
  • call/apply/bind 调用:this 指向指定对象
  • new 调用构造函数:this 指向新创建的实例
  • 箭头函数:this 继承自外层作用域,永不改变
相关推荐
遂心_14 分钟前
React初学者必备:用“状态管家”Reducer轻松管理复杂状态!
前端·javascript·react.js
用户33790448021721 分钟前
ECMA6 ---- Class篇 (重难点个人向)
javascript
李明卫杭州23 分钟前
前端实现多标签页通讯
前端·javascript
在钱塘江27 分钟前
《你不知道的JavaScript-上卷》第二部分-this和对象原型-笔记-6-行为委托
前端·javascript
Point28 分钟前
[ahooks] useControllableValue源码阅读
前端·javascript
HexCIer28 分钟前
cbT.js: 一个让模板继承变得优雅的 Node.js 模板引擎
javascript·node.js
独立开阀者_FwtCoder35 分钟前
踩坑无数后,我终于总结出这份最全的 Vue3 组件通信实战指南
前端·javascript·vue.js
20261 小时前
12. npm version方法总结
前端·javascript·vue.js
帅夫帅夫1 小时前
JavaScript继承探秘:从原型链到ES6 Class
前端·javascript
小赖同学啊2 小时前
将Blender、Three.js与Cesium集成构建物联网3D可视化系统
javascript·物联网·blender