1. thisJavaScript 核心关键字之一,但其指向并非在定义时确定,而是由调用方式和执行上下文动态决定;

  2. 理解 this 的关键在于:谁调用函数,this 就指向谁 (核心原则),但不同场景下有特殊规则;

this 的核心特性

  1. 动态绑定this 指向在函数执行时确定,而非定义时;

  2. 上下文依赖:不同调用方式 (普通调用、对象调用、构造函数等) 对应不同 this

  3. 严格模式影响:严格模式下避免 this 隐式指向全局对象;

  4. 可显式修改:通过 call/apply/bind 强制改变 this 指向;

不同场景下的 this 指向

场景 1:全局作用域中的 this

  1. 非严格模式:全局作用域 (浏览器 / Node.js) 中,this 指向全局对象:

    1. 浏览器:window
    2. Node.jsglobal (Node.js 14+ 后为 globalThis)
  2. 严格模式:全局作用域的 this 仍指向全局对象 (仅函数内严格模式有差异)

    // 浏览器环境
    console.log(this === window); // true(非严格/严格模式均成立)
    
    // Node.js 环境
    console.log(this === globalThis); // true
    

场景 2:普通函数调用(独立调用)

  1. 非严格模式:普通函数直接调用时,this 隐式指向全局对象 (浏览器 window,Node.js globalThis)

    function foo() {
      console.log(this); // 浏览器:window;Node.js:globalThis
    }
    foo(); // 独立调用
    
  2. 严格模式:严格模式下,普通函数调用的 thisundefined (避免隐式全局污染)

    'use strict';
    function foo() {
      console.log(this); // undefined
    }
    foo();
    

场景 3:对象方法调用

  1. 函数作为对象的方法调用时,this 指向调用该方法的对象 (核心规则:“谁调用,指向谁”)

  2. 基础示例:

    const obj = {
      name: '张三',
      sayHi: function() {
        console.log(this.name); // 张三(this 指向 obj)
      }
    };
    obj.sayHi(); // obj 调用方法,this → obj
    
  3. 嵌套对象 / 方法赋值的特殊情况:

    1. 方法被赋值给变量后独立调用:this 回退为全局对象 / undefined
    2. 嵌套对象的方法:this 指向直接调用者,而非外层对象;
    const obj = {
      name: '张三',
      sayHi: function() {
        console.log(this.name);
      }
    };
    const fn = obj.sayHi; 
    fn(); // 独立调用 → 非严格模式:undefined(全局无 name);严格模式:报错
    
    const outer = {
      name: '外层',
      inner: {
        name: '内层',
        sayHi: function() {
          console.log(this.name); // 内层(this 指向 inner)
        }
      }
    };
    outer.inner.sayHi();
    

场景 4:构造函数调用(new 关键字)

  1. 使用 new 调用函数时,函数成为构造函数,this 指向新创建的实例对象;

  2. 构造函数执行流程 (决定 this 指向)

    1. 创建一个空的新对象;
    2. 将新对象的 __proto__ 指向构造函数的 prototype
    3. 构造函数的 this 绑定到新对象;
    4. 若构造函数无显式返回值,返回新对象;若返回非对象类型,仍返回新对象;若返回对象类型,返回该对象;
    function Person(name) {
      this.name = name; // this 指向新实例
    }
    const p1 = new Person('李四');
    console.log(p1.name); // 李四
    console.log(this === p1); // false(this 是全局,p1 是实例)
    
    // 特殊:构造函数返回对象
    function Person2(name) {
      this.name = name;
      return { age: 20 }; // 返回对象,覆盖新实例
    }
    const p2 = new Person2('王五');
    console.log(p2.name); // undefined
    console.log(p2.age); // 20
    

场景 5:箭头函数中的 this

  1. 箭头函数是 ES6 新增,其 this 有本质区别:

    1. 箭头函数无自己的 thisthis 继承自定义时的外层普通函数 / 全局作用域;
    2. 箭头函数的 this 一旦确定,无法通过 call/apply/bind 修改;
    3. 箭头函数不能作为构造函数 (无 this,调用 new 会报错)
    4. 箭头函数无 arguments 对象 (可用剩余参数 …args 替代)
  2. 实例:

    const obj = {
      name: '赵六',
      sayHi: () => {
        console.log(this.name); // undefined(this 继承全局,全局无 name)
      },
      sayHi2: function() {
        const inner = () => {
          console.log(this.name); // 赵六(this 继承 sayHi2 的 this → obj)
        };
        inner();
      }
    };
    obj.sayHi(); 
    obj.sayHi2();
    
    const arrowFn = () => console.log(this);
    arrowFn.call({ name: '测试' }); // 仍指向全局(浏览器 window)
    

场景 6:DOM 事件处理函数中的 this

  1. 普通函数:DOM 事件回调中,this 指向触发事件的 DOM 元素;

  2. 箭头函数:this 继承外层作用域 (通常是全局 window)

    const btn = document.getElementById('btn');
    
    // 普通函数:this → btn 元素
    btn.onclick = function() {
      console.log(this); // <button id="btn">点击</button>
    };
    
    // 箭头函数:this → window
    btn.onclick = () => {
      console.log(this); // window
    };
    

场景 7:显式修改 this(call/apply/bind)

  1. JavaScript 提供 3 个方法强制修改函数的 this 指向,核心区别:

    方法 语法 执行时机 参数传递 返回值
    call fn.call(thisArg, arg1, arg2...) 立即执行 逐个传参 函数执行结果
    apply fn.apply(thisArg, [arg1, arg2...]) 立即执行 数组传参 函数执行结果
    bind fn.bind(thisArg, arg1, arg2...) 不立即执行 逐个传参 (可柯里化) 绑定 this 后的新函数
  2. 示例代码:

    function sum(a, b) {
      return this.base + a + b;
    }
    const obj = { base: 10 };
    
    // call:立即执行,逐个传参
    console.log(sum.call(obj, 2, 3)); // 15(this → obj)
    
    // apply:立即执行,数组传参
    console.log(sum.apply(obj, [2, 3])); // 15
    
    // bind:返回新函数,不立即执行
    const boundSum = sum.bind(obj, 2); // 柯里化,固定第一个参数
    console.log(boundSum(3)); // 15
    

常见坑点与解决方案

  1. 坑点 1

    1. 回调函数中 this 丢失:
      const obj = {
        data: [1,2,3],
        process: function() {
          // 回调函数独立调用,this → 全局/undefined
          this.data.forEach(function(item) {
            console.log(this); // window/undefined
          });
        }
      };
      obj.process();
      
    2. 解决方案:
      1. 用变量保存 thisconst that = this;
      2. 使用箭头函数 (继承外层 this)
      3. forEach 第二个参数指定 thisthis.data.forEach(..., this)
  2. 坑点 2

    1. 对象方法作为定时器回调
      const obj = {
        sayHi: function() {
          console.log(this); // window(定时器回调是普通调用)
        }
      };
      setTimeout(obj.sayHi, 1000);
      
    2. 解决方案:
      1. 包裹箭头函数:setTimeout(() => obj.sayHi(), 1000)
      2. bind 绑定 thissetTimeout(obj.sayHi.bind(obj), 1000)
  3. 坑点 3

    1. 箭头函数作为对象方法
      const obj = {
        name: '错误示例',
        fn: () => {
          console.log(this.name); // undefined(this 指向全局)
        }
      };
      obj.fn();
      
    2. 解决方案:改用普通函数作为对象方法;

面试题

手写 call、apply、bind

Function.prototype.myCall = function (context, ...args) {
    context === null ? context = window : null;

    //=> 此处 this 为 call 前的函数,也就是调用 call 的函数
    //=> 把函数赋值给需要指定 this 的对象 context
    context.$fn = this;
    let result = context.$fn(...args); // 这时函数执行 this 就变成了 context

    delete context.$fn;
    return result;
};

// 测试
const person = { name: '张三' };
function sayHi(age, gender) {
  console.log(`我是${this.name},年龄${age},性别${gender}`);
  return { name: this.name, age, gender };
}
console.log(sayHi.myCall(person, 20, '男')); // 我是张三,年龄20,性别男
console.log(sayHi.myCall(null, 25, '女')); // 我是 undefined,年龄25,性别女
Function.prototype.myApply = function (context, argsArr=[]) {
    context === null ? context = window : null;

    //=> 此处 this 为 call 前的函数,也就是调用 call 的函数
    //=> 把函数赋值给需要指定 this 的对象 context
    context.$fn = this;
    let result = context.$fn(...argsArr); // 这时函数执行 this 就变成了 context

    delete context.$fn;
    return result;
};

// 测试
const animal = { type: '猫' };
function describe(color, age) {
  console.log(`这是一只${color}的${this.type},年龄${age}岁`);
  return { type: this.type, color, age };
}

console.log('ES6 myApply:');
console.log(describe.myApply(animal, ['橘色', 3])); // 这是一只橘色的猫,年龄3岁
console.log(describe.myApply(animal)); // 输出:这是一只undefined的猫,年龄undefined岁
Function.prototype.myBind = function (context, ...bindArgs) {
  if (typeof this !== "function") {
    throw new TypeError("Bind must be called on a function");
  }

  return  (...callArgs) => this.apply(context, [...bindArgs, ...callArgs]);
};

// 测试
const person = { name: "张三" };
function sayHi(age, gender) {
  console.log(`我是${this.name},${age}岁,${gender}`);
}

// 绑定 this 和预设参数
const boundSayHi = sayHi.myBind(person, 20);
boundSayHi("男"); // 输出:我是张三,20岁,男

阿里原题

function fn1() {
  console.log(1);
}
function fn2() {
  console.log(2);
}

// fn2.$fn = fn1;
// fn2.$fn() => fn2.fn1();this 指向 fn2,执行 fn1,输出 1
fn1.call(fn2); // 1

// 把 fn1.call 看成 AF0,AF0.call(fn2)
// fn2.$fn() => fn2.AF0(); fn1.call 和 fn1.call.call 都是同一个 call 函数
// 则 AF0 可以看成 call 函数,即 fn2.call 执行,this 为 window,输出 2
fn1.call.call(fn2); // 2

// 同上
Function.prototype.call.call(fn2); // 2
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. this 的核心特性
  2. 2. 不同场景下的 this 指向
    1. 2.1. 场景 1:全局作用域中的 this
    2. 2.2. 场景 2:普通函数调用(独立调用)
    3. 2.3. 场景 3:对象方法调用
    4. 2.4. 场景 4:构造函数调用(new 关键字)
    5. 2.5. 场景 5:箭头函数中的 this
    6. 2.6. 场景 6:DOM 事件处理函数中的 this
    7. 2.7. 场景 7:显式修改 this(call/apply/bind)
  3. 3. 常见坑点与解决方案
  4. 4. 面试题
    1. 4.1. 手写 call、apply、bind
    2. 4.2. 阿里原题