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

相关推荐
阿乐去买菜2 小时前
2025 年末 TypeScript 趋势洞察:AI Agent 与 TS 7.0 的原生化革命
前端
POLITE32 小时前
Leetcode 438. 找到字符串中所有字母异位词 JavaScript (Day 4)
javascript·算法·leetcode
创思通信2 小时前
STM32F103C8T6采 DS18B20,通过A7680C 4G模块不断发送短信到手机
javascript·stm32·智能手机
海绵宝龙2 小时前
Vue 中的 Diff 算法
前端·vue.js·算法
zhougl9962 小时前
vue中App.vue和index.html冲突问题
javascript·vue.js·html
止观止2 小时前
告别全局污染:深入理解 ES Modules 模块化与构建工具
javascript·webpack·vite·前端工程化·es modules
袁煦丞 cpolar内网穿透实验室2 小时前
无需公网 IP 也能全球访问本地服务?cpolar+Spring Boot+Vue应用实践!
vue.js·spring boot·tcp/ip·远程工作·内网穿透·cpolar
浩泽学编程2 小时前
内网开发?系统环境变量无权限配置?快速解决使用其他版本node.js
前端·vue.js·vscode·node.js·js
狗哥哥2 小时前
Vue 3 插件系统重构实战:从过度设计到精简高效
前端·vue.js·架构