Skip to content

this 指向和改变 this 指向

要点速览

shell
# this 是什么
this JavaScript 中的一个关键字,它在函数执行时绑定,指向当前执行上下文的环境对象。简单来说,this 就是函数执行时的“调用者”。
this 不是固定不变的,它会随着执行环境的改变而改变。

# this 指向
重点:
  普通函数作为对象的方法调用则 this 指向该对象,否则指向全局对象(谁调用它指向谁)
  箭头函数的 this 指向箭头函数定义时的 外层 作用域
  构造函数指向实例化对象
- 普通函数:非严格模式指向window,严格模式指向undefined
- 对象方法:指向调用该方法的对象
- 构造函数:new调用时,指向新创建的实例,如果构造函数有返回值,则指向构造函数的 return 返回值
- 箭头函数:无自身this,this 指向定义位置外层作用域的 this,外侧没有函数则指向全局对象
- 事件绑定:指向触发事件的DOM元素
- 回调函数:回调函数的 this 会丢失,默认指向全局对象

# 改变 this 指向的三种方式 call、apply、bind
call apply 都是立即执行,call 参数逐个传,apply 参数数组传;
bind 不会立即执行,返回一个永久绑定 this 的新函数;参数可以分配传,也可以像 call 一样逐个传递

# 为何回调函数会丢 this
回调函数是由第三方(定时器、浏览器、数组、Promise)被动调用,不是对象主动调用。

# this 指向口诀
回调里对象方法 this 出错,是因为函数被剥离对象,变成普通函数调用
会出问题的场景:定时器、事件监听、数组方法、Promise、参数传递、方法赋值、class 方法
修复方案:箭头函数包裹 / bind() / 箭头函数定义方法
->
方法脱离对象单独传参、单独调用,this 必丢失;
箭头包裹、bind 绑定,万能修复。

拓展

shell

# 改变 this 指向的三种方式:apply、call、bind
通过函数的 apply、call、bind 可临时改变函数的 this 指向

# call
语法:函数.call(this 将指向的对象, '传入函数的第一个参数', '传入函数的第二个参数');
特点:以逗号分割形式传参,改变 this 后函数立即执行

# apply
语法:函数.apply(this 将指向的对象, ['传入函数的第一个参数','传入函数的第二个参数']);
特点:以数组形式传参,改变 this 后函数立即执行

# bind
语法:函数.bind(this 将指向的对象, ['传入函数的第一个参数','传入函数的第二个参数']);
特点:以逗号分割或者柯里化形式传参,改变 this 后函数不立即执行,而是返回一个绑定了 this 执行的新函数,再次调用才会执行,支持柯里化传入部分参数

# 注意
传入 null / undefined this 自动替换成 window
传入基本类型(数字 / 字符串)→ 自动包装成包装对象
箭头函数不能被 call/apply/bind 修改 this
箭头函数没有自身 this,传参无效,会被忽略
new 绑定优先级 > bind > call/apply > 隐式绑定 > 默认绑定
- 是否指向全局对象还要看是否是严格模式,非严格模式 this 指向的全局对象 window,严格模式输出 undefined
- 解构后方法调用会丢失 this 绑定

# 继承相关
重点:把apply、call、bind方法的第一个对象参数放入构造函数中作为 this 执行一遍,此对象参数将重新挂载一遍构造函数的属性和方法
也相当于函数在调用的那一刻,将 this 执行了传入的第一个对象参数,也就是临时改变函数 this 指向

# bind 能多次绑定吗?
不能,第一次绑定永久生效

# new 调用 bind 后的函数,this 指向谁?
指向 new 出来的实例,忽略绑定的 context

this 丢失和修复原因

shell
# 定时器内回调函数的 this 指向
如果回调函数是 普通函数 则看谁调用的定时器,定时器是 window 的方法,因此是 window 调用,指向 window
如果回调函数是 箭头函数 则,指向定时器外层作用域,一般定时器内的回调常使用箭头函数,因为可以修复 this 指向(跨层修复)

# 方法中的函数的 this 指向
普通函数指向调用它的对象,this 指向对象
箭头函数指向 内层是对象,即:对象的外层作用域

this 丢失和修复示例

场景 1:对象方法单独赋值调用

js
/********* 错误示例 ********/
const obj = {
  name: "张三",
  fn() {
    console.log(this.name);
  }
};

// 正常调用
obj.fn(); // 张三

// 提取方法赋值给变量,this丢失
const fn = obj.fn;
fn(); // undefined

/********* 修复方法 ********/
// 方式1:bind绑定
const fn1 = obj.fn.bind(obj);
fn1();

// 方式2:包裹函数(箭头函数返回函数,fn2 是一个方法,内部的 obj.fn() 是完整的对象调用方法,不是 obj.fn)
const fn2 = () => obj.fn();
fn2();

场景 2:setTimeout /setInterval 回调

js
/********* 错误示例 ********/
const obj = {
  name: "李四",
  hello() {
    console.log(this.name);
  }
};

setTimeout(obj.hello, 1000); // undefined

/********* 修复方法 ********/
// 方式1:bind绑定
// 1. 箭头函数包裹
setTimeout(() => obj.hello(), 1000);

// 2. bind 绑定
setTimeout(obj.hello.bind(obj), 1000);

场景 3:DOM 事件绑定

js
/********* 错误示例 ********/
const btn = document.querySelector('button');
const obj = {
  msg: "对象里的文案",
  handleClick() {
    console.log(this.msg); // undefined,this 指向 btn
  }
};

btn.addEventListener('click', obj.handleClick);

/********* 修复方法 ********/
btn.addEventListener('click', () => obj.handleClick());
btn.addEventListener('click', obj.handleClick.bind(obj));

场景 4:数组方法 forEach /map 回调

js
/********* 错误示例 ********/
const obj = {
  num: 100,
  log() {
    console.log(this.num);
  }
};

[1,2,3].forEach(obj.log); // 全是 undefined

/********* 修复方法 ********/
// 1. bind 绑定
[1,2,3].forEach(obj.log.bind(obj));

// 2. 外层包裹
[1,2,3].forEach(() => obj.log());

// 3. forEach 第二个参数绑定this
[1,2,3].forEach(obj.log, obj);

场景 5:class 类方法回调丢失(严格模式)

React class类组件

js
/********* 错误示例 ********/
class Person {
  constructor() {
    this.name = "小明";
  }
  sayHi() {
    console.log(this.name);
  }
}
const p = new Person();

setTimeout(p.sayHi, 1000); // 报错:读不到 undefined 属性

/********* 修复方法 ********/
// 1. 构造器里提前 bind
class Person {
  constructor() {
    this.name = "小明";
    this.sayHi = this.sayHi.bind(this);
  }
  sayHi() {
    console.log(this.name);
  }
}

// 2. 调用时箭头包裹
setTimeout(() => p.sayHi(), 1000);

// 3. 类中直接用箭头函数定义
class Person {
  name = "小明";
  sayHi = () => {
    console.log(this.name);
  }
}

场景 6:对象内部嵌套普通函数

js
/********* 错误示例 ********/
const obj = {
  name: "王五",
  foo() {
    function bar() {
      console.log(this.name); // undefined
    }
    bar();
  }
};
obj.foo();

/********* 修复方法 ********/
// 改成箭头函数,继承外层this
const obj = {
  name: "王五",
  foo() {
    const bar = () => {
      console.log(this.name); // 王五
    };
    bar();
  }
};
obj.foo();

补充:调用形式的 this(合并自辨析篇)

最后一跳「谁调用」决定 this。普通函数在非严格模式下,脱离对象裸露调用时趋向全局对象:

js
function fn1() {}
const obj = { a: { fn1 } };
fn1();                    // 全局(严格模式为 undefined)
obj.a.fn1();              // obj.a

const res = obj.a.fn1;
res();                     // 又变回裸露调用 → 全局或 undefined(严格)

function F1() {
  console.log(this);       // new 时为实例
}
new F1();

构造函数带 对象类型 return 时,this 可能被返回值覆盖(基础类型则忽略)。

js
// 箭头函数词法 this:来自「定义处」外层的 this(此处为 ctx)
const ctx = {
  name: 9,
  run() {
    const o = {
      name: 'obj',
      f1: () => console.log(this.name), // 9
    };
    o.f1();
  },
};
ctx.run();

call / apply / bind:首参为目标 thisnull|undefined 在非严格常被替换为全局;箭头函数忽略这些绑定。

js
function f1(p1, p2) {
  console.log(this.name, p1, p2);
}
f1.call({ name: 'c' }, 'a', 'b');
f1.apply({ name: 'a' }, ['x', 'y']);
f1.bind({ name: 'b' })('m', 'n');

类实例方法解构后丢失接收者 → 与「方法赋值给变量」同类问题:

js
class MyClass {
  constructor() {
    this.value = 42;
  }
  getValue() {
    return this.value;
  }
}
const instance = new MyClass();
const { getValue } = instance;
getValue(); // TypeError:this 为 undefined(严格模式)