Skip to content

深入理解 this 绑定

五种绑定规则、优先级链、箭头函数特殊性、所有边界 case 一网打尽

什么是 this?

定义this 是函数执行时的上下文对象引用。与大多数语言不同,JavaScript 的 this 不是在定义时确定的(箭头函数除外),而是在调用时根据调用方式动态绑定的。它的值取决于函数"怎么被调用",而非"在哪里定义"。

涉及场景

  • 对象方法调用obj.method()this 指向 obj
  • 构造函数new Constructor()this 指向新创建的实例
  • 事件处理器:DOM 事件回调中 this 指向触发事件的元素
  • 定时器/回调setTimeoutthis 丢失是经典陷阱
  • React 类组件:方法中 this 丢失需要手动 bind 或用箭头函数
  • call/apply/bind:手动指定函数执行时的 this

作用

  1. 对象行为绑定:让方法能够访问所属对象的属性和其他方法
  2. 代码复用:同一个函数通过不同的 this 在不同对象上工作
  3. 面向对象基础:构造函数和原型方法都依赖 this 访问实例
  4. 高频面试考点this 指向判断题是面试必考项

五种绑定规则

1. 默认绑定(独立调用)

函数独立调用时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。严格模式下为 undefined

javascript
function showThis() {
  console.log(this);
}

showThis(); // window(非严格模式)

// 严格模式
'use strict';
function showThisStrict() {
  console.log(this);
}
showThisStrict(); // undefined

// 常见陷阱:嵌套函数的独立调用
const obj = {
  name: '张三',
  greet() {
    console.log(this.name); // '张三'(隐式绑定)

    function inner() {
      console.log(this.name); // undefined(默认绑定!)
      // inner 是独立调用的,不是 obj.inner()
    }
    inner();
  }
};
obj.greet();

// IIFE 也是独立调用
const obj2 = {
  name: '李四',
  method() {
    (function() {
      console.log(this); // window(默认绑定)
    })();
  }
};

2. 隐式绑定(对象方法调用)

函数作为对象的方法调用时,this 指向调用它的对象

javascript
const obj = {
  name: '张三',
  greet() {
    return `Hello, ${this.name}`;
  }
};

console.log(obj.greet()); // 'Hello, 张三'

// 链式调用:this 指向最近的调用者
const a = {
  name: 'a',
  b: {
    name: 'b',
    getName() {
      return this.name;
    }
  }
};
console.log(a.b.getName()); // 'b'(this 指向 b,不是 a)

// ⚠️ 隐式丢失:将方法赋值给变量
const fn = obj.greet;
console.log(fn()); // undefined(变成了默认绑定!)

// ⚠️ 隐式丢失:作为回调传递
function execute(callback) {
  callback(); // 独立调用
}
execute(obj.greet); // undefined

// ⚠️ 隐式丢失:setTimeout
setTimeout(obj.greet, 100); // undefined

// ⚠️ 隐式丢失:数组方法
const handlers = [obj.greet];
handlers[0](); // undefined(虽然是 handlers[0]() 调用,但函数引用已经丢失了obj上下文)
// 实际上 handlers[0]() 的 this 指向 handlers 数组

// 解决方案
setTimeout(() => obj.greet(), 100); // ✅ 箭头函数保持上下文
setTimeout(obj.greet.bind(obj), 100); // ✅ bind 显式绑定

3. 显式绑定(call / apply / bind)

使用 callapplybind 明确指定 this

javascript
function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`;
}

const user = { name: '张三' };

// call - 逐个传参
greet.call(user, 'Hello', '!');    // 'Hello, 张三!'

// apply - 数组传参
greet.apply(user, ['Hello', '!']); // 'Hello, 张三!'

// bind - 返回新函数,永久绑定this
const boundGreet = greet.bind(user, 'Hello');
boundGreet('!');  // 'Hello, 张三!'
boundGreet('?');  // 'Hello, 张三?'

call vs apply vs bind 对比:

方法执行时机参数形式返回值
call立即执行逐个传参 fn.call(ctx, a, b)函数返回值
apply立即执行数组传参 fn.apply(ctx, [a, b])函数返回值
bind不执行,返回新函数逐个传参(可柯里化)新函数
javascript
// 显式绑定的特殊情况:传入 null/undefined
function foo() {
  console.log(this);
}

foo.call(null);      // window(非严格模式,忽略null)
foo.call(undefined); // window

// 严格模式下保持 null/undefined
'use strict';
foo.call(null);      // null
foo.call(undefined); // undefined

// 安全做法:使用空对象
const empty = Object.create(null);
foo.call(empty); // {}(不会意外修改全局对象)

4. new 绑定(构造函数调用)

使用 new 调用函数时,this 指向新创建的对象

javascript
function Person(name, age) {
  // this = {} (新创建的对象)
  this.name = name;
  this.age = age;
  // return this; (隐式返回)
}

const p = new Person('张三', 25);
console.log(p.name); // '张三'

// 特殊情况:构造函数显式返回对象
function Strange(name) {
  this.name = name;
  return { custom: true }; // 返回对象 → 替代 this
}
const s = new Strange('张三');
console.log(s.name);   // undefined
console.log(s.custom); // true

// 返回非对象 → 忽略返回值,使用 this
function Normal(name) {
  this.name = name;
  return 42; // 返回原始值 → 被忽略
}
const n = new Normal('张三');
console.log(n.name); // '张三'

5. 箭头函数绑定(词法 this)

箭头函数没有自己的 this,它继承定义时外层函数的 this。

javascript
const obj = {
  name: '张三',
  // 普通方法
  greetNormal() {
    setTimeout(function() {
      console.log(this.name); // undefined(默认绑定)
    }, 100);
  },
  // 箭头函数
  greetArrow() {
    setTimeout(() => {
      console.log(this.name); // '张三'(继承 greetArrow 的 this)
    }, 100);
  }
};

// 箭头函数不能用 call/apply/bind 改变 this
const arrow = () => console.log(this);
arrow.call({ name: '张三' }); // window(call 无效!)
arrow.bind({ name: '张三' })(); // window(bind 也无效!)

// 箭头函数不能用作构造函数
const Arrow = () => {};
// new Arrow(); // ❌ TypeError: Arrow is not a constructor

// ⚠️ 箭头函数不适合做对象方法
const bad = {
  name: '张三',
  greet: () => {
    console.log(this.name); // undefined
    // 箭头函数的 this 是定义时的外层(全局),不是 bad
  }
};

// ✅ 正确做法:用普通函数做对象方法
const good = {
  name: '张三',
  greet() {
    console.log(this.name); // '张三'
  }
};

绑定优先级

new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定(obj.fn) > 默认绑定(fn())
javascript
// 验证优先级

// 1. 隐式 vs 显式:显式胜
const obj = {
  name: 'obj',
  greet() { return this.name; }
};
console.log(obj.greet.call({ name: 'call' })); // 'call'(显式 > 隐式)

// 2. 显式 vs new:new 胜
function Foo(name) {
  this.name = name;
}
const bound = Foo.bind({ name: 'bind' });
const instance = new bound('new');
console.log(instance.name); // 'new'(new > 显式)

// 3. 箭头函数无视所有规则
const arrowObj = {
  name: 'obj',
  getArrow() {
    return () => this.name; // this 固定为 getArrow 的 this
  }
};
const arrow = arrowObj.getArrow();
console.log(arrow.call({ name: 'call' })); // 'obj'(箭头函数无视 call)

特殊场景

class 中的 this

javascript
class MyClass {
  name = '张三';

  // 普通方法 - this 取决于调用方式
  greet() {
    return this.name;
  }

  // 箭头函数属性 - this 永远指向实例
  greetArrow = () => {
    return this.name;
  };

  // 静态方法 - this 指向类本身
  static create() {
    console.log(this === MyClass); // true
    return new this('created');
  }
}

const obj = new MyClass();

// 普通方法:隐式丢失
const fn = obj.greet;
// fn(); // ❌ TypeError(class 默认严格模式,this 是 undefined)

// 箭头函数属性:不会丢失
const fn2 = obj.greetArrow;
console.log(fn2()); // '张三' ✅

// React 中的经典问题
class Button {
  text = 'Click me';

  // ❌ 传给事件处理器时 this 丢失
  handleClick() {
    console.log(this.text);
  }

  // ✅ 方案1:箭头函数属性
  handleClick2 = () => {
    console.log(this.text);
  };

  render() {
    // ✅ 方案2:bind
    element.addEventListener('click', this.handleClick.bind(this));
    // ✅ 方案3:箭头函数包装
    element.addEventListener('click', () => this.handleClick());
    // ✅ 方案4:使用箭头函数属性
    element.addEventListener('click', this.handleClick2);
  }
}

DOM 事件中的 this

javascript
const button = document.querySelector('#btn');

// addEventListener - this 指向绑定元素
button.addEventListener('click', function(e) {
  console.log(this === button);    // true
  console.log(this === e.target);  // true(如果直接点击该按钮)
  console.log(this === e.currentTarget); // true
});

// 箭头函数 - this 是外层作用域
button.addEventListener('click', (e) => {
  console.log(this === window); // true(箭头函数不绑定this)
  console.log(e.target);       // button(通过 e.target 访问)
});

// onclick 属性 - this 指向元素
button.onclick = function() {
  console.log(this); // button
};

// HTML内联事件 - this 指向元素
// <button onclick="console.log(this)">Click</button>
// this → button 元素

回调函数中的 this

javascript
// 数组方法的 thisArg 参数
const obj = { factor: 2 };
const arr = [1, 2, 3];

// forEach/map/filter/find/some/every 都支持第二个参数指定 this
arr.map(function(item) {
  return item * this.factor;
}, obj); // [2, 4, 6]

// 注意:箭头函数作为回调时,thisArg 参数无效
arr.map((item) => {
  return item * this.factor; // this 是外层,不受 thisArg 影响
}, obj);

// JSON.parse reviver 中的 this
JSON.parse('{"a": 1, "b": 2}', function(key, value) {
  // this 指向当前解析的对象
  console.log(key, value, this);
  return value;
});

globalThis(ES2020)

javascript
// 统一访问全局对象
console.log(globalThis); // 在任何环境中都指向全局对象

// 之前需要根据环境判断:
// 浏览器:window / self / frames
// Node.js:global
// Web Worker:self

// globalThis 统一了这些差异
globalThis.myGlobal = 'accessible everywhere';

手写 call / apply / bind

手写 call

javascript
Function.prototype.myCall = function(context, ...args) {
  // null/undefined → 全局对象
  context = context == null ? globalThis : Object(context);

  // 用 Symbol 避免属性名冲突
  const key = Symbol('fn');
  context[key] = this;

  // 通过对象方法调用,实现隐式绑定
  const result = context[key](...args);

  delete context[key];
  return result;
};

// 测试
function greet(greeting) {
  return `${greeting}, ${this.name}`;
}
console.log(greet.myCall({ name: '张三' }, 'Hello')); // 'Hello, 张三'

手写 apply

javascript
Function.prototype.myApply = function(context, argsArray = []) {
  context = context == null ? globalThis : Object(context);

  const key = Symbol('fn');
  context[key] = this;

  const result = context[key](...argsArray);

  delete context[key];
  return result;
};

手写 bind

javascript
Function.prototype.myBind = function(context, ...outerArgs) {
  if (typeof this !== 'function') {
    throw new TypeError('must be a function');
  }

  const fn = this;

  const bound = function(...innerArgs) {
    // new 调用时,this 指向新实例,忽略绑定的 context
    return fn.apply(
      this instanceof bound ? this : context,
      [...outerArgs, ...innerArgs]
    );
  };

  // 维护原型链
  if (fn.prototype) {
    bound.prototype = Object.create(fn.prototype);
  }

  return bound;
};

经典面试题

题目1:综合判断

javascript
var name = 'window';

const obj = {
  name: 'obj',
  fn1: function() {
    console.log(this.name);
  },
  fn2: () => {
    console.log(this.name);
  },
  fn3: function() {
    return function() {
      console.log(this.name);
    };
  },
  fn4: function() {
    return () => {
      console.log(this.name);
    };
  }
};

obj.fn1();           // 'obj'(隐式绑定)
obj.fn2();           // 'window'(箭头函数,外层是全局)
obj.fn3()();         // 'window'(返回的函数独立调用 → 默认绑定)
obj.fn4()();         // 'obj'(箭头函数继承 fn4 的 this)

const fn1 = obj.fn1;
fn1();               // 'window'(隐式丢失 → 默认绑定)

obj.fn3.call({ name: 'call' })(); // 'window'(call 改变 fn3 的 this,但返回的函数独立调用)
obj.fn4.call({ name: 'call' })(); // 'call'(箭头函数继承 fn4 的 this,fn4 被 call 绑定为 { name: 'call' })

题目2:new vs bind

javascript
function Foo() {
  this.a = 1;
  return this;
}

Foo.prototype.getA = function() {
  return this.a;
};

// 场景1:普通调用
const result1 = Foo();
console.log(result1.a);   // 1(this 是 window,window.a = 1)

// 场景2:new 调用
const result2 = new Foo();
console.log(result2.a);   // 1(this 是新对象)

// 场景3:bind 后 new
const BoundFoo = Foo.bind({ a: 100 });
const result3 = new BoundFoo();
console.log(result3.a);   // 1(new 优先级高于 bind)
console.log(result3.getA()); // 1

题目3:setTimeout 中的 this

javascript
const obj = {
  x: 42,
  getX() {
    return this.x;
  }
};

// 各种调用方式
console.log(obj.getX());                    // 42(隐式绑定)
console.log(obj.getX.call({ x: 100 }));     // 100(显式绑定)

setTimeout(obj.getX, 0);                     // undefined(隐式丢失)
setTimeout(obj.getX.bind(obj), 0);           // 42(bind 修复)
setTimeout(() => console.log(obj.getX()), 0); // 42(箭头函数保持)

const arr = [obj.getX];
console.log(arr[0]());                       // undefined(隐式丢失)

判断 this 的流程图

调用 fn() 时,判断 this:

1. fn 是箭头函数吗?
   → 是:this = 定义时外层函数的 this(无法修改)
   → 否:继续 ↓

2. fn 是通过 new 调用的吗?
   → 是:this = 新创建的对象
   → 否:继续 ↓

3. fn 是通过 call/apply/bind 调用的吗?
   → 是:this = 传入的第一个参数(null/undefined 在非严格模式下转为全局对象)
   → 否:继续 ↓

4. fn 是通过 obj.fn() 调用的吗?
   → 是:this = obj
   → 否:继续 ↓

5. 默认绑定
   → 非严格模式:this = 全局对象(window/global)
   → 严格模式:this = undefined

总结

this 绑定核心规则:
┌──────────────────────────────────────────────────────────┐
│ 规则          │ 调用方式        │ this 指向               │
├───────────────┼────────────────┼────────────────────────┤
│ 默认绑定      │ fn()           │ window / undefined      │
│ 隐式绑定      │ obj.fn()       │ obj                    │
│ 显式绑定      │ fn.call(ctx)   │ ctx                    │
│ new 绑定      │ new Fn()       │ 新对象                  │
│ 箭头函数      │ () => {}       │ 外层 this(不可变)      │
├──────────────────────────────────────────────────────────┤
│ 优先级:new > 显式 > 隐式 > 默认                           │
│ 箭头函数:无视所有规则,永远继承外层                          │
├──────────────────────────────────────────────────────────┤
│ 常见陷阱:                                                │
│ • 方法赋值给变量 → 隐式丢失                                │
│ • setTimeout 回调 → 隐式丢失                              │
│ • 箭头函数做对象方法 → this 不指向对象                       │
│ • class 方法作为回调 → 需要 bind 或箭头函数属性              │
└──────────────────────────────────────────────────────────┘