Skip to content

this 绑定机制

this 是 JavaScript 中最容易出错的概念。它不是在定义时确定,而是在调用时根据调用方式动态绑定。

四种绑定规则

1. 默认绑定

js
function greet() {
  console.log(this)
}

greet()  // 非严格模式:window / global;严格模式:undefined

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

2. 隐式绑定

js
const user = {
  name: 'Alice',
  greet() {
    console.log(this.name)  // this → user
  }
}

user.greet()  // 'Alice'

// ⚠️ 隐式丢失:赋值给变量后丢失绑定
const fn = user.greet
fn()  // undefined(this → window/undefined)

// ⚠️ 回调中丢失绑定
setTimeout(user.greet, 0)  // undefined

3. 显式绑定

js
function greet(greeting) {
  return `${greeting}, ${this.name}`
}

const alice = { name: 'Alice' }
const bob   = { name: 'Bob' }

// call:立即调用,参数逐个传入
greet.call(alice, 'Hello')   // 'Hello, Alice'

// apply:立即调用,参数以数组传入
greet.apply(bob, ['Hi'])     // 'Hi, Bob'

// bind:返回新函数,永久绑定 this
const greetAlice = greet.bind(alice)
greetAlice('Hey')  // 'Hey, Alice'
greetAlice.call(bob, 'Hey')  // 'Hey, Alice'(bind 后无法再改变 this)

4. new 绑定

js
function Person(name) {
  // new 调用时,this 指向新创建的对象
  this.name = name
  this.greet = function() {
    console.log(this.name)
  }
}

const alice = new Person('Alice')
alice.greet()  // 'Alice'

优先级

new 绑定 > 显式绑定(call/apply/bind)> 隐式绑定 > 默认绑定
js
function foo() { console.log(this.x) }

const obj1 = { x: 1, foo }
const obj2 = { x: 2, foo }

obj1.foo()           // 1(隐式)
obj1.foo.call(obj2)  // 2(显式 > 隐式)

const BoundFoo = foo.bind(obj1)
BoundFoo.call(obj2)  // 1(bind 后显式绑定无效)

const instance = new BoundFoo()  // new 绑定 > bind
// this 指向新对象,x 为 undefined

箭头函数:词法 this

箭头函数没有自己的 this,它捕获定义时所在作用域的 this,且无法被改变。

js
const timer = {
  seconds: 0,

  // ❌ 普通函数:this 丢失
  startWrong() {
    setInterval(function() {
      this.seconds++  // this → window,不是 timer
    }, 1000)
  },

  // ✅ 箭头函数:捕获外层 this
  start() {
    setInterval(() => {
      this.seconds++  // this → timer ✓
    }, 1000)
  }
}

// 箭头函数的 this 无法被 call/apply/bind 改变
const arrow = () => console.log(this)
arrow.call({ x: 1 })  // 仍然是外层的 this,不是 { x: 1 }

类方法中的箭头函数

js
class Button {
  constructor(label) {
    this.label = label
  }

  // ❌ 普通方法:作为回调时 this 丢失
  handleClickWrong() {
    console.log(this.label)
  }

  // ✅ 类字段箭头函数:this 永远指向实例
  handleClick = () => {
    console.log(this.label)  // 始终正确
  }
}

const btn = new Button('Submit')
document.addEventListener('click', btn.handleClick)  // ✓
document.addEventListener('click', btn.handleClickWrong)  // ✗ this 丢失

手写 call / apply / bind

js
// 手写 call
Function.prototype.myCall = function(context, ...args) {
  context = context ?? globalThis
  const key = Symbol()  // 避免属性名冲突
  context[key] = this   // this 是被调用的函数
  const result = context[key](...args)
  delete context[key]
  return result
}

// 手写 apply
Function.prototype.myApply = function(context, args = []) {
  context = context ?? globalThis
  const key = Symbol()
  context[key] = this
  const result = context[key](...args)
  delete context[key]
  return result
}

// 手写 bind
Function.prototype.myBind = function(context, ...preArgs) {
  const fn = this
  return function(...args) {
    // 如果通过 new 调用,this 指向新对象,忽略绑定的 context
    if (new.target) {
      return new fn(...preArgs, ...args)
    }
    return fn.apply(context, [...preArgs, ...args])
  }
}

React 中的 this 问题

jsx
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }

    // 方式一:构造函数中 bind
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 })
  }

  // 方式二:类字段箭头函数(推荐)
  handleClickArrow = () => {
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    return (
      <div>
        {/* 方式三:内联箭头函数(每次渲染创建新函数,性能差)*/}
        <button onClick={() => this.handleClick()}>Click</button>

        {/* 推荐:使用类字段箭头函数 */}
        <button onClick={this.handleClickArrow}>Click</button>
      </div>
    )
  }
}

总结

  • this 在调用时确定,不是定义时
  • 四种规则优先级:new > 显式 > 隐式 > 默认
  • 箭头函数没有自己的 this,捕获外层词法 this
  • 类方法作为回调时会丢失 this,用箭头函数类字段解决
  • bind 返回的函数,this 无法再被改变(除了 new

系统学习 Web 前端生态,深入底层架构