JS 中的 call,到底在“控制什么”?一篇讲清楚,让你想忘都难

JS 中的 call,到底在“控制什么”?一篇讲清楚,让你想忘都难 JS 中的 call到底在“控制什么”一篇讲清楚让你想忘都难1️⃣ 问题背景真实场景你有没有写过这样的代码functionsay(){console.log(this.name);}constuser{name:Tom};say();// ❌ undefined或 window.namesay.call(user);// ✅ Tom 很多人知道call 可以改变 this但问题是❗ 为什么它能改❗ 它到底“改”的是什么❗ 和 bind / apply 本质差在哪如果你只是记“用法”那你迟早会在复杂场景翻车。2️⃣ 核心问题本质分析call本质解决的是❗ “函数执行时this 指向谁” 的问题换句话说call 强行指定函数运行时的 this但这句话还不够深。真正本质是❗ JS 中函数的 this 不是定义时决定而是调用时决定3️⃣ 原理讲解通俗但深入✅ 先看一个关键认知functionfn(){}这个fn其实是一个对象fn.__proto__Function.prototype而call就定义在这里Function.prototype.call 所以fn.call(...)本质是Function.prototype.call.call(fn,...) call 做了什么一句话总结让一个对象“临时拥有这个函数”然后执行它 模拟实现核心理解Function.prototype.myCallfunction(context,...args){// 1. 防止 context 为 null / undefinedcontextcontext||window;// 2. 给对象挂一个临时方法constfnKeySymbol();context[fnKey]this;// 3. 执行函数constresultcontext[fnKey](...args);// 4. 删除临时方法deletecontext[fnKey];returnresult;}; 核心动作只有三步挂函数用对象调用删除函数❗ 为什么这样就能改变 this因为obj.fn() this 就是 obj所以context.fn() this 就变成了 context4️⃣ 常见错误或误区❌ 误区 1call 是“绑定 this”错fn.call(obj) 是“立即执行”不是绑定❌ 误区 2call 和 bind 一样fn.call(obj);// 立即执行fn.bind(obj);// 返回新函数 bind 才是“绑定”call 是“执行”❌ 误区 3箭头函数可以用 call 改 thisconstfn(){console.log(this);};fn.call({a:1});// ❌ 无效 箭头函数的 this 是❗ 定义时决定词法作用域5️⃣ 解决方案分层说明 场景一借用函数constobj1{name:Tom,};constobj2{name:Jerry,};functionsay(){console.log(this.name);}say.call(obj1);// Tomsay.call(obj2);// Jerry 场景二类数组转数组functiontest(){constargsArray.prototype.slice.call(arguments);console.log(args);} 本质借用数组方法处理“伪数组” 场景三继承经典面试题functionParent(name){this.namename;}functionChild(name){Parent.call(this,name);} 作用把 Parent 的属性“拷贝”到 Child 实例6️⃣ 工程实现真实项目场景 场景事件回调 this 丢失classMap{constructor(){this.zoom10;}init(){document.addEventListener(click,this.handleClick);}handleClick(){console.log(this.zoom);// ❌ undefined}}✅ 正确做法用 call 修正classMap{constructor(){this.zoom10;}init(){document.addEventListener(click,(e){this.handleClick.call(this,e);});}handleClick(){console.log(this.zoom);// ✅ 10}} 真实踩坑很多人直接document.addEventListener(click,this.handleClick.bind(this));✔ 能用但❗ 每次 bind 都会创建新函数 → 难以 removeEventListener❗ 性能和管理都变差7️⃣ 架构或设计思路 call 的设计体现了 JS 的一个核心哲学❗ 函数是“可被任意对象复用的能力”而不是函数属于某个对象 ❌ 对象可以借用函数 ✅ 这就是 JS 和面向对象语言的关键区别语言函数归属Java属于类JS独立存在8️⃣ 性能或优化建议⚠️ 高频调用场景避免 callfor(leti0;i100000;i){fn.call(obj);} 会有额外开销属性挂载删除操作作用域处理✅ 优化方式constboundFnfn.bind(obj);for(leti0;i100000;i){boundFn();} bind 一次复用多次9️⃣ 总结认知提升 把这一句话记住❗ call 的本质 改变函数执行时的 this并立即执行再深一层❗ 本质是把函数“挂到对象上再调用” 延伸思考 如果你已经理解 call可以继续思考apply 为什么存在bind 为什么返回函数new 调用时 this 为什么优先级最高箭头函数为什么无法被 call 改变完结撒花✿✿ヽ(°▽°)ノ✿