Skip to content

函数调用

一、什么是函数调用?

函数调用是执行函数体的过程。当一个函数被定义后,它不会自动执行,只有通过调用才能运行函数内部的代码。

函数调用的本质

js
function greet(name) {
  return `Hello, ${name}!`
}

greet('World')

当调用 greet('World') 时,发生了以下步骤:

  1. 创建执行上下文 - 为函数调用创建一个新的执行环境
  2. 绑定 this - 根据调用方式确定 this 的值
  3. 绑定参数 - 将实参传递给形参
  4. 执行函数体 - 运行函数内部的代码
  5. 返回值 - 返回执行结果(如果没有显式返回,则返回 undefined

函数调用的核心要素

每次函数调用都会涉及两个重要概念:

概念说明
this函数执行时的上下文对象,取决于调用方式
arguments函数调用时传入的实参列表(类数组对象)

二、函数调用方式及其优缺点

JavaScript 中有多种函数调用方式,不同的调用方式会决定 this 的绑定规则。

1. 普通函数调用(直接调用)

最基础的调用方式,函数名后直接加括号。

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

sayHi()

this 绑定规则

  • 非严格模式this 指向全局对象(浏览器中是 window,Node.js 中是 global
  • 严格模式thisundefined
js
function strictMode() {
  'use strict'
  console.log(this)
}

strictMode()

优点

优点说明
语法简洁直接使用函数名调用,代码清晰
适合工具函数不依赖上下文的纯函数,如数学计算、数据处理

缺点

缺点说明
this 不确定容易丢失上下文,导致意外行为
难以控制上下文无法指定函数内部的 this 指向

典型问题示例

js
const obj = {
  name: 'Alice',
  greet: function () {
    console.log(`Hello, ${this.name}`)
  }
}

const fn = obj.greet
fn()

2. 方法调用

函数作为对象的方法被调用,通过对象来访问函数。

js
const person = {
  name: 'Bob',
  greet: function () {
    console.log(`Hello, ${this.name}`)
  }
}

person.greet()

this 绑定规则

this 指向调用该方法的对象(点号前面的对象)。

js
const obj1 = {
  name: 'Object1',
  greet: function () {
    console.log(this.name)
  }
}

const obj2 = {
  name: 'Object2'
}

obj2.greet = obj1.greet
obj2.greet()

优点

优点说明
this 明确this 指向调用对象,语义清晰
面向对象编程符合 OOP 思想,便于封装数据和行为
代码组织性好相关数据和方法组织在同一对象中

缺点

缺点说明
容易丢失 this回调函数中容易丢失上下文
方法不能独立使用脱离对象调用会丢失绑定

典型问题示例

js
const button = {
  text: 'Click me',
  click: function () {
    console.log(this.text)
  }
}

document.querySelector('button').addEventListener('click', button.click)

解决方案

js
document
  .querySelector('button')
  .addEventListener('click', button.click.bind(button))

document.querySelector('button').addEventListener('click', () => button.click())

3. 构造函数调用(new 调用)

使用 new 关键字调用函数,创建一个新的对象实例。

js
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.introduce = function () {
  console.log(`I'm ${this.name}, ${this.age} years old.`)
}

const alice = new Person('Alice', 25)
alice.introduce()

new 调用的执行过程

js
function _new(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype)
  const result = Constructor.apply(obj, args)
  return result instanceof Object ? result : obj
}
  1. 创建空对象 - 创建一个新的空对象
  2. 设置原型链 - 将新对象的原型指向构造函数的 prototype
  3. 绑定 this - 将构造函数的 this 绑定到新对象
  4. 执行构造函数 - 运行构造函数内部代码
  5. 返回对象 - 如果构造函数返回对象则返回该对象,否则返回新创建的对象

this 绑定规则

this 指向新创建的实例对象

优点

优点说明
创建对象实例可以批量创建结构相同的对象
原型继承通过 prototype 实现属性和方法共享
类型识别可以通过 instanceof 判断对象类型

缺点

缺点说明
忘记使用 new如果忘记 newthis 会指向全局对象,造成污染
不够直观与普通函数在语法上没有区别,容易误用

防止忘记 new 的技巧

js
function Person(name) {
  if (!(this instanceof Person)) {
    return new Person(name)
  }
  this.name = name
}

Person('Alice')

4. call() 调用

使用 Function.prototype.call() 方法调用函数,可以显式指定 this

js
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`)
}

const person = { name: 'Alice' }

greet.call(person, 'Hello', '!')

语法

js
func.call(thisArg, arg1, arg2, ...)
参数说明
thisArg函数执行时的 this 值(传 nullundefined 时指向全局对象)
arg1, arg2, ...逐个传递的参数

this 绑定规则

this 指向 call 的第一个参数。

优点

优点说明
灵活控制 this可以显式指定函数执行的上下文
参数逐个传递适合参数较少的情况
借用方法可以借用其他对象的方法

缺点

缺点说明
参数传递繁琐参数多时需要逐个列出
代码可读性一般相比直接调用,语法稍显复杂

典型应用场景

js
const arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
}

const arr = Array.prototype.slice.call(arrayLike)
console.log(arr)

function getMax() {
  return Math.max.call(null, ...arguments)
}

console.log(getMax(1, 5, 3, 9, 2))

5. apply() 调用

使用 Function.prototype.apply() 方法调用函数,与 call 类似,但参数以数组形式传递。

js
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`)
}

const person = { name: 'Bob' }

greet.apply(person, ['Hi', '?'])

语法

js
func.apply(thisArg, argsArray)
参数说明
thisArg函数执行时的 this
argsArray参数数组或类数组对象

this 绑定规则

call 相同,this 指向第一个参数。

优点

优点说明
适合数组参数当参数已经是数组时,使用更方便
与数组方法配合可以与 argumentsNodeList 等配合使用
动态参数数量参数数量不确定时更灵活

缺点

缺点说明
需要构造数组参数少时反而不如 call 方便
可读性一般语法不如直接调用直观

call vs apply 对比

js
function introduce(name, age, city) {
  console.log(`${name}, ${age}岁, 来自${city}`)
}

introduce.call(null, 'Alice', 25, '北京')
introduce.apply(null, ['Bob', 30, '上海'])

const args = ['Charlie', 28, '广州']
introduce.apply(null, args)

典型应用场景

js
const numbers = [5, 6, 2, 3, 7]

const max = Math.max.apply(null, numbers)
console.log(max)

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
Array.prototype.push.apply(arr1, arr2)
console.log(arr1)

6. bind() 调用

bind() 方法不会立即执行函数,而是返回一个新函数,新函数的 this 被永久绑定。

js
function greet() {
  console.log(`Hello, ${this.name}`)
}

const person = { name: 'Alice' }

const boundGreet = greet.bind(person)
boundGreet()

语法

js
func.bind(thisArg, arg1, arg2, ...)
参数说明
thisArg绑定的 this
arg1, arg2, ...预设的参数(柯里化)

this 绑定规则

返回的新函数无论以何种方式调用,this 都指向绑定的值。

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

const person = { name: 'Alice' }
const boundGreet = greet.bind(person)

boundGreet()
boundGreet.call({ name: 'Bob' })
boundGreet.apply({ name: 'Charlie' })

const obj = { name: 'David', fn: boundGreet }
obj.fn()

优点

优点说明
永久绑定 this不会丢失上下文,非常可靠
延迟执行返回新函数,可以在需要时调用
支持柯里化可以预设部分参数
适合回调函数解决回调函数丢失 this 的问题

缺点

缺点说明
创建新函数每次调用都创建新函数,可能影响性能
不能再改变 this绑定后无法再次绑定或使用 call/apply 改变
不适合构造函数bind 返回的函数不能作为构造函数使用

典型应用场景

js
const module = {
  x: 42,
  getX: function () {
    return this.x
  }
}

const unboundGetX = module.getX
console.log(unboundGetX())

const boundGetX = unboundGetX.bind(module)
console.log(boundGetX())

function list() {
  return Array.prototype.slice.call(arguments)
}

const list1 = list(1, 2, 3)

const leadingThirtysevenList = list.bind(null, 37)
const list2 = leadingThirtysevenList()
const list3 = leadingThirtysevenList(1, 2, 3)

console.log(list1)
console.log(list2)
console.log(list3)

class Button {
  constructor(text) {
    this.text = text
  }

  click() {
    console.log(`Clicked: ${this.text}`)
  }
}

const btn = new Button('Submit')
document.addEventListener('click', btn.click.bind(btn))

7. 箭头函数调用

箭头函数是 ES6 引入的特殊函数,它没有自己的 this,而是继承外层作用域的 this

js
const person = {
  name: 'Alice',
  greet: function () {
    const arrowGreet = () => {
      console.log(`Hello, ${this.name}`)
    }
    arrowGreet()
  }
}

person.greet()

this 绑定规则

箭头函数的 this定义时确定,继承自外层作用域,无法被改变。

js
const person = {
  name: 'Alice',
  greet: () => {
    console.log(this.name)
  }
}

person.greet()

const obj = {
  name: 'Bob',
  fn: function () {
    const arrow = () => console.log(this.name)
    arrow()
  }
}

obj.fn()

优点

优点说明
this 继承外层不需要担心 this 丢失问题
语法简洁适合简单的回调函数
适合回调场景在数组方法、定时器等回调中非常方便

缺点

缺点说明
不能绑定 this无法使用 call/apply/bind 改变 this
不能作为构造函数不能使用 new 调用
没有 arguments 对象需要使用 rest 参数代替
不适合对象方法作为对象方法时 this 可能不是预期值

典型应用场景

js
const obj = {
  name: 'Alice',
  friends: ['Bob', 'Charlie'],
  showFriends: function () {
    this.friends.forEach(friend => {
      console.log(`${this.name} knows ${friend}`)
    })
  }
}

obj.showFriends()

function Timer() {
  this.seconds = 0
  setInterval(() => {
    this.seconds++
    console.log(this.seconds)
  }, 1000)
}

const timer = new Timer()

const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => n * 2)
console.log(doubled)

三、调用方式对比总结

调用方式this 绑定是否立即执行能否改变 this典型场景
普通调用全局对象/undefined工具函数
方法调用调用对象对象方法
new 调用新实例对象创建对象
call()第一个参数借用方法
apply()第一个参数数组参数
bind()第一个参数否(返回新函数)否(永久绑定)回调函数
箭头函数外层作用域回调、数组方法

四、this 绑定优先级

当多种绑定方式同时出现时,优先级从高到低为:

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

const obj = { name: 'obj' }
const boundFoo = foo.bind(obj)

new boundFoo()

优先级验证

js
function foo() {
  console.log(this.a)
}

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

obj1.foo()
obj1.foo.call(obj2)

const boundFoo = obj1.foo.bind(obj2)
boundFoo()

new boundFoo()

五、最佳实践

1. 根据场景选择合适的调用方式

js
// 工具函数 - 普通调用
function add(a, b) {
  return a + b
}

// 对象方法 - 方法调用
const calculator = {
  value: 0,
  add(n) {
    this.value += n
    return this
  }
}

// 创建对象 - new 调用
function User(name) {
  this.name = name
}

// 回调函数 - bind 或箭头函数
class Component {
  constructor() {
    this.state = {}
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    console.log(this.state)
  }
}

2. 避免在回调中丢失 this

js
// 错误示例
const obj = {
  name: 'Alice',
  greet() {
    setTimeout(function () {
      console.log(this.name)
    }, 1000)
  }
}

// 解决方案1:使用箭头函数
const obj1 = {
  name: 'Alice',
  greet() {
    setTimeout(() => {
      console.log(this.name)
    }, 1000)
  }
}

// 解决方案2:使用 bind
const obj2 = {
  name: 'Alice',
  greet() {
    setTimeout(
      function () {
        console.log(this.name)
      }.bind(this),
      1000
    )
  }
}

// 解决方案3:保存 this 引用
const obj3 = {
  name: 'Alice',
  greet() {
    const self = this
    setTimeout(function () {
      console.log(self.name)
    }, 1000)
  }
}

3. 使用严格模式避免意外

js
'use strict'

function foo() {
  console.log(this)
}

foo()

六、总结

函数调用是 JavaScript 中最基础也最重要的概念之一。理解不同调用方式下 this 的绑定规则,是掌握 JavaScript 的关键。

核心要点:

  1. 普通调用this 指向全局对象或 undefined(严格模式)
  2. 方法调用this 指向调用该方法的对象
  3. new 调用this 指向新创建的实例
  4. call/apply:显式指定 this,立即执行
  5. bind:返回新函数,永久绑定 this
  6. 箭头函数:继承外层作用域的 this

选择合适的调用方式,可以让代码更加清晰、可维护,避免 this 相关的 bug。

基于 VitePress 的本地知识库