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

相关推荐
岳哥i2 小时前
vue鼠标单机复制文本
javascript
jacGJ3 小时前
记录学习--文件读写
java·前端·学习
毕设源码-赖学姐3 小时前
【开题答辩全过程】以 基于WEB的实验室开放式管理系统的设计与实现为例,包含答辩的问题和答案
前端
幻云20103 小时前
Python深度学习:从筑基到登仙
前端·javascript·vue.js·人工智能·python
我即将远走丶或许也能高飞5 小时前
vuex 和 pinia 的学习使用
开发语言·前端·javascript
钟离墨笺5 小时前
Go语言--2go基础-->基本数据类型
开发语言·前端·后端·golang
爱吃泡芙的小白白5 小时前
Vue 3 核心原理与实战:从响应式到企业级应用
前端·javascript·vue.js
卓怡学长6 小时前
m115乐购游戏商城系统
java·前端·数据库·spring boot·spring·游戏
码上成长6 小时前
JavaScript 数组合并性能优化:扩展运算符 vs concat vs 循环 push
开发语言·javascript·ecmascript
老陈聊架构6 小时前
『AI辅助Skill』掌握三大AI设计Skill:前端独立完成产品设计全流程
前端·人工智能·claude·skill