别再猜 this:先看它属于谁,再看它指向谁

为什么要有 this

this 是 JavaScript 的关键字,用来隐式传递当前对象的引用。函数不必写死某个对象的名称,也不必额外接收对象参数,就能读取或修改调用者的数据。

这使函数只需要描述"做什么",调用方式则决定"为谁执行"。同一个函数可以被不同对象复用,代码也因此更加简洁。

this 可以出现在哪

1. 全局作用域

在浏览器的普通脚本中,全局作用域里的 this 指向 window

js 复制代码
console.log(this);
console.log(this === window); // true

2. 函数体内

普通函数中的 this 不是在函数定义时确定的,而是由函数的调用方式决定。同一个函数换一种调用方式,this 就可能发生变化。

因此,分析 this 时要始终抓住一句话:

先看这个 this 属于谁,再看它指向谁。

  • this 属于谁 :先找到 this 写在哪里。普通函数有自己的 this;箭头函数没有自己的 this,需要继续向外寻找,直到找到最近的非箭头函数或全局作用域。
  • this 指向谁 :找到真正拥有这个 this 的位置后,再结合调用方式判断。普通函数看它怎样被调用;全局作用域则直接根据运行环境判断。

全局作用域中的 this 可以直接判断;函数体内的 this 则需要结合下面的绑定规则分析。

this 的绑定规则

绑定规则解决的是第二步:已经知道 this 属于哪个函数后,再根据这个函数的调用形式判断 this 最终指向谁。

常见调用形式与绑定结果如下:

调用形式 绑定规则 this 的指向
foo() 默认绑定 window
obj.foo() 隐式绑定 obj
oo.foo.foo() 多层调用 离函数最近的调用对象
foo.call(obj) / foo.apply(obj) / foo.bind(obj)() 显式绑定 指定的 obj
new Foo() new 绑定 新创建的实例

1. 默认绑定

函数以 foo() 的形式独立执行,并且 this 没有被显式绑定或 new 绑定改变时,会使用默认绑定。此时函数中的 this 指向 window

js 复制代码
var a = 1;

function bar() {
    var a = 2;

    function foo() {
        console.log(this.a);
    }

    foo();
}

bar(); // 1

这段代码可以分三步判断:

  1. this 出现在 foo 中。
  2. foo() 是独立调用,使用默认绑定,所以 this 指向 window
  3. 浏览器普通脚本中,顶层通过 var 声明的 a 会成为 window.a,因此输出 1

bar 内部的 a 是局部变量。函数嵌套关系不会改变 foothis,所以这里不会输出 2

2. 隐式绑定

当函数保存在对象属性中,并通过这个对象调用时,函数中的 this 指向该对象。

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

var obj = {
    a: 1,
    foo: foo
};

obj.foo(); // 1

对象属性 foo 保存的是函数引用。真正决定 this 的是调用表达式 obj.foo():调用点前有 obj,所以 foo 中的 this 指向 obj,最终读取到 obj.a

如果同一个函数由另一个对象调用,this 也会随之改变:

js 复制代码
var another = {
    a: 2,
    foo: foo
};

another.foo(); // 2

这正是 this 便于复用的原因:函数本身没有改变,调用者决定了它操作哪个对象。

3. 隐式丢失:多层对象调用

多层对象调用不会把最外层对象一直传递给函数。判断时只看最终调用点,this 指向离函数最近的调用对象。

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

var obj = {
    a: 1,
    foo: foo
};

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

oo.foo.foo(); // 1

oo.foo 的值是 obj,所以 oo.foo.foo() 可以看成由 obj 调用 foo。直接调用者是 obj,而不是最外层的 oo,因此 this.a 的结果是 1

4. 显式绑定

不想由调用形式自动判断时,可以使用 callapplybind 直接指定函数中的 this

js 复制代码
function foo(x, y) {
    console.log(this.a, x + y);
}

var GGbond = { a: 1 };
var FF = { a: 2 };
var CRQ = { a: 3 };

foo.call(GGbond, 1, 2); // 1 3
foo.apply(FF, [2, 3]);  // 2 5

const bar = foo.bind(CRQ, 3, 4);
bar(); // 3 7

三种方法都能指定 this,区别在于调用时机和参数形式:

  • call 立即调用函数,从第二个参数开始逐个传递实参。
  • apply 立即调用函数,使用数组传递实参。
  • bind 不会立即调用,而是返回一个绑定好 this 的新函数,需要再次调用。

5. new 绑定

函数通过 new 调用时,其中的 this 指向新创建的实例对象。构造函数中通过 this 添加的属性,也会出现在这个实例上。

js 复制代码
function Person() {
    this.name = "GGBond";
}

const p = new Person();
console.log(p.name); // GGBond

调用 new Person() 时,Person 中的 this 指向实例 p,所以 this.name = "GGBond" 等同于给 p 添加 name 属性。

箭头函数

箭头函数没有自己的 this,也不使用普通函数的绑定规则。它会沿用定义位置外层作用域中的 this;当箭头函数写在普通函数内部时,它会沿用该普通函数的 this

js 复制代码
function foo() {
    var fn = () => {
        this.a = 2;
    };

    fn();
}

var obj = {
    a: 1,
    bar: foo
};

obj.bar();
console.log(obj.a); // 2

这段代码的执行过程是:

  1. obj.bar() 调用普通函数 foo,所以 foothis 指向 obj
  2. 箭头函数 fn 没有自己的 this,于是沿用 foothis
  3. this.a = 2 实际修改的是 obj.a,最终输出 2

箭头函数不能作为构造函数,因此不能通过 new 调用。

普通函数的 this 取决于调用方式;箭头函数的 this 取决于定义位置的外层作用域。抓住这一区别,就能快速判断代码中的 this 指向。

相关推荐
YIAN20 分钟前
React + Zustand + JWT 前端权限体系完整实现:从登录鉴权到路由守卫全流程拆解
前端·react.js·架构
汉堡大王952724 分钟前
面试必考:手写代码 new 做了什么?从原理到实现全解析
前端·javascript·面试
BillKu1 小时前
TypeScript中,字符串字面量联合类型(Union Type)、enum的用法说明
前端·javascript·typescript
jayson.h1 小时前
PDF 合并+添加页码 相关库、类、函数
开发语言·前端·python
慧一居士2 小时前
Sass和Less功能、使用场景、用法对比
前端·css·less·sass
leoZ2312 小时前
AI+前端提效-09 AI赋能前端测试:单元测试、E2E测试自动生成,提升覆盖率
前端·人工智能·opencv·目标检测·数据挖掘·单元测试·语音识别
计算机魔术师2 小时前
OpenAI 评定 Astra 达到网络安全 Critical 能力阈值,将受限发布
前端
JavaGuide2 小时前
SpaceX 工程师的 AI Coding 玩法太牛了, 200 多个 Agent 并行!
前端·后端
小猪code2 小时前
frp 80/443 Web 服务两种部署模式实操笔记
前端·笔记