Skip to content

手写 call / apply / bind

来源:js/实现/this.htmljs/实现/new.html,手写实现 callapplybind 改变 this 指向。

共享执行环境:代码块间可互相访问变量和函数定义

此页面启用共享 iframe 模式,所有代码块共享执行环境。

速查导航


核心思路

call/apply/bind 本质上就是:给目标对象临时添加一个方法并执行,执行完再删除。

js

简单理解:

  • call/apply/bind 就是普通方法,跟 join 等没区别
  • 执行函数时将函数内的 this 指向第一个参数,同时传入其余参数
  • 第一个参数为 null/undefined 时指向 window

手写 call

js

手写 apply

call 区别:第二个参数是数组。

注意: 原生 apply 会检查第二个参数是否为数组或类数组对象,传入非数组会报 TypeError。手写版如果没有做类型判断,传入非数组不会报错但行为可能不符合预期。

js

手写 bind

bindcall/apply 不同:不立即执行,返回一个改变了 this 的函数。

参数两次截取:

  1. 绑定时截取:...args 获取 bind(fn, arg1, arg2) 中的参数
  2. 执行时截取:...arg 获取返回函数被调用时的参数
  3. 最终合并:[...arg, ...args] 执行时参数在前,绑定时参数在后
js
js

bind 与 new

bind 返回的函数被 new 调用时,this 指向实例而非 con

this instanceof fBound ? this : con 的含义:

  • new 调用时 this 指向实例 → instanceoftrue → 用 this(忽略 con
  • 普通调用时 this 指向 windowinstanceoffalse → 用 con

如果写反了会怎样?

  • 改成 this instanceof fBound ? null : connew 调用时 this 被替换为 null,实例变成空对象,无法获得绑定函数上的属性(如 habit
  • 普通调用不受影响,仍指向 con

原型链处理:

  • fNOP.prototype = this.prototype; fBound.prototype = new fNOP();
  • 用中间函数 fNOP 避免直接修改 fBound.prototype = this.prototype 导致原函数原型被污染
  • bind 返回的函数作为构造函数时,实例能继承原函数原型上的属性

Function.prototype.apply.call

当目标函数自身定义了 apply 方法时,不能直接用 fn.apply(obj, args),因为会调用到函数自己的 apply 而非原生的。

js

总结: Function.prototype.apply.call(fn, thisArg, args) 是一种安全的调用方式,确保使用原生的 apply 而非对象自定义的方法。