JS中的call apply bind全面解析

JavaScript 中的 call / apply / bind 全面解析 + Vue 实战指南


一、它们是什么?

call / apply / bind 都是 函数对象的方法,用于:

  • 改变函数内部 this 指向
  • 控制函数执行方式
  • 复用函数
  • 控制执行时机
js 复制代码
function show() {
  console.log(this.name); // 打印当前 this 的 name 属性
}

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

show();          // this = window/globalThis → 输出 undefined(严格模式下)
show.call(obj);  // this = obj → 输出 '张三'
function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: '张三' };
// 1️⃣ call:立即执行,参数逐个传入
greet.call(person, '你好', '!');  
// this = person, 逐个传参
// 打印结果: "你好, 张三!"

// 2️⃣ apply:立即执行,参数数组传入
greet.apply(person, ['早上好', '你吃了吗']); 
// this = person, 参数用数组
// 打印结果: "早上好, 张三你吃了吗"

// 3️⃣ bind:不执行,返回新函数
const boundGreet = greet.bind(person, '晚上好', '!!!'); 
// 返回一个新函数,但不执行
boundGreet();  
// 执行后打印结果: "晚上好, 张三!!!"

二、区别总结

方法 是否立即执行 参数写法 典型用途
call ✔ 立即执行 fn.call(obj, a, b) 改 this、方法借用
apply ✔ 立即执行 fn.apply(obj, [a, b]) 改 this、数组参数
bind ❌ 不执行 fn.bind(obj, a, b)(返回新函数) 延迟执行、绑定 this

三、为什么需要?

JavaScript 中 this 会丢失:

js 复制代码
function show() {
  console.log(this.name);
}
const obj = { name: '李四' };
// 直接调用
show(); // undefined
// 使用 call 改变 this
show.call(obj); // 李四

四、call ------ 逐个参数 + 立即执行

js 复制代码
function sum(a, b) {
  console.log(this.label, a + b); // 输出 this.label 与 a+b
}

const obj = { label: '结果:' };

// 使用 call 改变 this 并传参
sum.call(obj, 10, 20); //30

五、apply ------ 数组参数 + 立即执行

js 复制代码
// 与 call 不同,参数必须传数组
sum.apply(obj, [10, 20]); //30

经典用途:

js 复制代码
const arr = [5, 9, 1, 20];

// 求数组最大值
console.log(Math.max.apply(null, arr)); // 20

// 把 arguments 转数组
function test() {
  const args = Array.prototype.slice.apply(arguments);
  console.log(args);
}

test(1, 2, 3, 4); // [1,2,3,4]

六、bind ------ 不执行,返回新函数

js 复制代码
function showMessage(msg) {
  console.log(this.name, msg);
}

const obj = { name: '李四' };

// bind 不执行,而是返回新函数
const boundFn = showMessage.bind(obj, '你好!');

// 调用新函数
boundFn(); //李四 你好!

七、ES6 写法对比

1️⃣ apply → 扩展运算符

js 复制代码
const arr = [5, 3, 9];
// 旧写法
console.log(Math.max.apply(null, arr)); // 9
// ES6 写法
console.log(Math.max(...arr)); // 9

2️⃣ arguments 转数组

js 复制代码
function demo() {
  // 旧写法
  const arr1 = Array.prototype.slice.apply(arguments);
  console.log('旧写法:', arr1); //旧写法: [1, 2, 3]

  // ES6
  const arr2 = [...arguments];
  console.log('ES6:', arr2);//ES6: [1, 2, 3]
}

demo(1,2,3);

3️⃣ bind → 箭头函数固定 this

js 复制代码
const obj2 = { name: '小明' };
// 箭头函数 this = 定义时作用域
setTimeout(() => console.log(obj2.name), 1000); //1s后打印小明

4️⃣ bind 柯里化 vs ES6

旧:

js 复制代码
function add(a,b){ return a+b }
const add5 = add.bind(null, 5)
console.log(add5(10)) //15

ES6:

js 复制代码
const add = a => b => a + b
console.log(add(5)(10)) //15

5️⃣ call → 可用对象方法替代

js 复制代码
const obj3 = {
  say() { console.log('Hi') }
}
obj3.say?.() //Hi

八、手写 call / apply / bind(完整版)


8.1 手写 call

js 复制代码
Function.prototype.myCall = function (context, ...args) {
  context = context ?? globalThis          // null/undefined 默认 globalThis
  context = Object(context)                // 原始值自动装箱
  const fnKey = Symbol('fn')              // 避免覆盖属性
  context[fnKey] = this                    // this = 当前函数
  const result = context[fnKey](...args)   // 执行函数
  delete context[fnKey]                     // 删除临时属性
  return result
}

// 测试
function say(a, b) {
  console.log(this.name, a + b) 
}

const obj = { name: '张三' }
say.myCall(obj, 1, 2) // 张三 3

8.2 手写 apply

js 复制代码
Function.prototype.myApply = function (context, args) {
  context = context ?? globalThis
  context = Object(context)
  const fnKey = Symbol('fn')
  context[fnKey] = this
  const result = args ? context[fnKey](...args) : context[fnKey]()
  delete context[fnKey]
  return result
}

// 测试
function sum(a, b) {
  console.log(this.label, a + b)
}

const obj2 = { label:'结果:' }
sum.myApply(obj2, [10, 20])//结果: 30

8.3 手写 bind(基础版)

js 复制代码
Function.prototype.myBind = function (context, ...presetArgs) {
  const self = this
  return function (...laterArgs) {
    return self.apply(context, [...presetArgs, ...laterArgs])
  }
}

// 测试
function show(a,b) {
  console.log(this.name, a, b)
}

const obj3 = { name: '李四' }
const fn = show.myBind(obj3, 1)
fn(2) //李四 1 2

8.4 bind(高级版 --- 支持 new)

js 复制代码
Function.prototype.myBind = function(context, ...presetArgs) {
  const self = this
  function boundFn(...laterArgs) {
    if (this instanceof boundFn) {           // new 优先级
      return new self(...presetArgs, ...laterArgs)
    }
    return self.apply(context, [...presetArgs, ...laterArgs])
  }
  boundFn.prototype = Object.create(self.prototype) // 保留原型链
  return boundFn
}

// 测试 new 场景
function Person(name){
  this.name = name
}

const BindPerson = Person.myBind({ name:'AAA' })
const p = new BindPerson('张三')

console.log(p.name)                    // 张三
console.log(p instanceof Person)       // true
console.log(p instanceof BindPerson)   // true

九、Vue 实战部分(详细版)

9.1 回调中 this 丢失 → bind

js 复制代码
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    handle() {
      console.log('count:', this.count)
      this.count++
    },
    test() {
      setTimeout(this.handle.bind(this), 1000) //1秒后打印count: 0
      // 不 bind 的话 this = window,count 会报错
    }
  }
}

9.2 防抖/节流绑定 this

js 复制代码
methods: {
  search() {
    console.log('搜索关键词:', this.keyword)
  },
  debounce(fn, delay) {
    let timer = null
    return function(...args) {
      clearTimeout(timer)
      timer = setTimeout(() => fn.apply(this, args), delay)
    }
  }
},
created() {
  this.searchDebounced = this.debounce(this.search, 500).bind(this)
}

9.3 父组件调用子组件方法 → call/apply

js 复制代码
// 父组件
this.$refs.child.say.call(this.$refs.child, 1,2)
this.$refs.child.say.apply(this.$refs.child, [1,2])

9.4 Vue3 setup

js 复制代码
import { reactive } from 'vue'

function useLog() {
  function log() {
    console.log(this.msg)
  }
  return log
}

export default {
  setup() {
    const state = reactive({ msg: 'hello' })
    const log = useLog().bind(state)
    log() // hello
  }
}

9.5 处理 $refs 类数组 → apply/ES6

js 复制代码
mounted() {
  const arr = Array.prototype.slice.apply(this.$refs.item)
  console.log(Array.isArray(arr)) // true

  const arr2 = [...this.$refs.item]
  console.log(Array.isArray(arr2)) // true
}

9.6 事件监听解绑必须 bind

js 复制代码
mounted() {
  this.onScroll = this.handleScroll.bind(this)
  window.addEventListener('scroll', this.onScroll)
},
beforeDestroy() {
  window.removeEventListener('scroll', this.onScroll)
},
methods:{
  handleScroll() {
    console.log('滚动事件', this)
  }
}

十、总结

✔ call → 改 this + 立即执行 + 逐个参数

✔ apply → 改 this + 立即执行 + 数组参数

✔ bind → 不执行 + 返回新函数(Vue 中最常用)

✔ ES6 可替代部分场景 → ... / Array.from / 箭头函数 / 柯里化

✔ 框架源码 & 高阶 JS 场景仍离不开原生 call/apply/bind

相关推荐
leobertlan1 小时前
2025年终总结
前端·后端·程序员
子兮曰2 小时前
OpenClaw架构揭秘:178k stars的个人AI助手如何用Gateway模式统一控制12+通讯频道
前端·javascript·github
Howrun7772 小时前
VSCode烦人的远程交互UI讲解
ide·vue.js·vscode
百锦再3 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
莲华君3 小时前
React快速上手:从零到项目实战
前端·reactjs教程
百锦再3 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs
易安说AI3 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
颜酱4 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
失忆爆表症5 小时前
05_UI 组件库集成指南:Shadcn/ui + Tailwind CSS v4
前端·css·ui
小迷糊的学习记录5 小时前
Vuex 与 pinia
前端·javascript·vue.js