无需登录 数据私有 本地保存

JavaScript this 上下文测试器 - 不同调用方式下的指向

14
0
0
0

JavaScript this 上下文测试器

直观理解不同调用方式下 this 的指向变化。点击下方场景卡片的「运行测试」按钮,观察实际执行结果。

普通函数
调用者 / window
对象方法
调用该方法的对象
new 构造
新创建的实例
call / apply
指定的参数对象
箭头函数
继承外层作用域
严格模式
undefined
预设场景测试
全局作用域调用
function showThis() {
  return this;
}
showThis(); // 非严格模式
严格模式调用
function showThis() {
  'use strict';
  return this;
}
showThis();
对象方法调用
const obj = {
  name: '测试对象',
  showThis() {
    return this;
  }
};
obj.showThis();
构造函数 new 调用
function Person(name) {
  this.name = name;
  this.getThis = function() {
    return this;
  };
}
const p = new Person('Alice');
p.getThis();
call() 显式绑定
function showThis() {
  return this;
}
const ctx = { role: 'call上下文' };
showThis.call(ctx);
apply() 显式绑定
function sum(a, b) {
  return { this: this, result: a + b };
}
const ctx = { name: 'apply上下文' };
sum.apply(ctx, [3, 7]);
bind() 永久绑定
function showThis() {
  return this;
}
const bound = showThis.bind({ id: 'bound' });
bound();
箭头函数 this
const outer = {
  name: '外层对象',
  method() {
    const arrow = () => this;
    return arrow();
  }
};
outer.method();
嵌套函数 this 丢失
const obj = {
  name: '外层',
  outer() {
    function inner() {
      return this;
    }
    return inner();
  }
};
obj.outer();
DOM 事件处理
const btn = document.querySelector('#myBtn');
btn.addEventListener('click', function() {
  return this; // → 触发事件的DOM元素
});
setTimeout 回调
setTimeout(function() {
  const capturedThis = this;
  // capturedThis → ?
}, 100);
ES6 Class 方法
class Animal {
  constructor(type) {
    this.type = type;
  }
  getThis() {
    return this;
  }
}
const dog = new Animal('dog');
dog.getThis();
自定义测试区

编写一个函数体(使用 this),选择调用方式,观察 this 的指向。

提示:箭头函数的 this 在定义时确定,继承自外层作用域;普通函数的 this 在调用时确定,取决于调用方式。call/apply/bind 无法改变箭头函数的 this。
常见问题与知识点

this 的指向不是在定义时决定的,而是在运行时根据调用方式决定的。普通函数中,谁调用函数,this 就指向谁。具体规则:
① 作为普通函数调用 → this 指向全局对象(window),严格模式下为 undefined;
② 作为对象方法调用 → this 指向调用该方法的对象;
③ 使用 new 调用 → this 指向新创建的实例对象;
④ 使用 call/apply/bind → this 指向指定的参数对象;
⑤ 箭头函数 → this 继承自外层作用域(定义时确定,无法被改变)。

这是面试中最常见的问题之一。核心区别:
普通函数:this 是动态的,在调用时确定,取决于调用方式。可以被 call/apply/bind 改变。
箭头函数:this 是静态的,在定义时确定,继承自外层最近的非箭头函数作用域。call/apply/bind 无法改变箭头函数的 this。

典型示例:在对象方法中使用箭头函数,箭头函数的 this 继承自方法的外层作用域(可能是全局),而不会指向该对象。这就是为什么在 Vue/React 组件方法中常用箭头函数来保持 this 指向组件实例。

三者都可以显式绑定函数的 this:
call(thisArg, arg1, arg2, ...):立即调用函数,参数逐个传递。
apply(thisArg, [argsArray]):立即调用函数,参数以数组形式传递。
bind(thisArg, arg1, arg2, ...):不立即调用,返回一个新函数,新函数的 this 被永久绑定。bind 还可以预设部分参数(柯里化)。

记忆技巧:call = comma(逗号分隔参数),apply = array(数组参数),bind = bound(返回绑定后的新函数)。

这是 JavaScript 中非常常见的陷阱。当在对象方法内部定义嵌套的普通函数时,嵌套函数作为普通函数被调用,其 this 指向全局对象(或严格模式下的 undefined),而不是外层对象。

解决方案:
① 使用箭头函数(推荐):const inner = () => { console.log(this); },箭头函数继承外层方法的 this;
② 保存 this 引用:const self = this;,在嵌套函数中使用 self;
③ 使用 bind:function inner() { ... }.bind(this)
④ 使用 call/apply 显式传递 this。

在严格模式('use strict')下,普通函数调用中的 this 不会默认指向全局对象,而是 undefined。这有助于避免意外修改全局变量。

需要注意的是:
• ES6 模块(使用 import/export)默认就是严格模式;
• class 内部也是严格模式;
• 使用 call/apply/bind 时,严格模式下传入的原始值不会被装箱(不会被转换为对象),而非严格模式下原始值会被包装成对应的对象类型(如字符串→String对象)。

使用 addEventListener 绑定的普通函数事件处理器中,this 指向触发事件的 DOM 元素(即 event.currentTarget)。
但如果使用箭头函数作为事件处理器,this 则继承自外层作用域,不会指向 DOM 元素。

在 HTML 内联事件属性(如 onclick="...")中,this 也指向该 DOM 元素。在 React 等框架中,由于合成事件机制,需要注意 this 绑定的处理方式。

bind 的优先级非常高,一旦函数被 bind 绑定,后续再使用 call/apply 也无法改变其 this。甚至多次 bind 也只有第一次 bind 生效。

优先级排序(从低到高):
普通调用 < call/apply < bind < new 构造调用
注意:new 调用的优先级高于 bind。如果对 bind 绑定后的函数使用 new,this 会指向新创建的实例,而不是 bind 绑定的对象。

数组的 map、forEach、filter、find 等方法接受第二个可选参数 thisArg,用于指定回调函数中的 this:
arr.forEach(function(el) { console.log(this); }, myContext);

如果使用箭头函数作为回调,则无法通过 thisArg 改变 this(箭头函数的 this 在定义时已确定)。这是箭头函数和普通函数在数组方法中的一个重要区别。
快速判断 this 的口诀

① 看调用:谁点出来的就是谁(obj.fn() → obj)
② 看 new:new 出来的就是新实例
③ 看箭头:箭头函数看外层(定义时确定)
④ 看 call/apply/bind:传谁就是谁
⑤ 啥都没有:非严格→window,严格→undefined

常见坑点速查

⚠️ 对象方法中的嵌套普通函数 this 丢失 → 用箭头函数
⚠️ setTimeout 回调 this 指向 window → 用箭头函数或 bind
⚠️ 事件处理用箭头函数拿不到 DOM 元素 → 用 e.currentTarget
⚠️ React 类组件方法需要 bind 或用箭头函数
⚠️ 严格模式下普通调用 this 为 undefined