50行JavaScript源码解析:手写实现call、apply与bind

作者:异步漫步时间:2026-10-23 10:12:10浏览:90

本文深入解析JavaScript中this关键字的四种指向规则,并通过50行核心代码逐步手写实现原生方法call、apply和bind。文章详细阐述了利用Symbol创建临时属性劫持this的机制,对比了三种方法在参数传递与返回值上的差异,并提供了完整的bind高阶实现,帮助开发者彻底掌握函数绑定的底层原理。

JavaScript中this指向的核心原理

在尝试手写实现call、apply和bind之前,有必要先回顾一下JavaScript中this关键字的工作机制。

我们可以将this形象地理解为一个指向当前执行上下文的指针。

理解this的核心原则只有一条:它始终指向最后调用该函数的对象,这是掌握其行为的关键所在。

通常情况下,this的指向分为以下四种场景:

当函数以普通方式直接调用时,在非严格模式下this指向全局对象window;若处于严格模式('use strict'),则this的值为undefined。


function test() {
 console.log(this);
};

test();
指向window 输出下面的代码:
// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}

严格模式
'use strict';
function test() {
 console.log(this);
};
test();
// undefined

当函数作为对象的方法被调用,即采用obj.fn()的形式时,this指向该对象obj。


var obj = {
 name: 'segmentFault',
 foo: function() {
  console.log(this.name);
 }
}
obj.foo();
// 'segmentFault'

这种调用方式也可以通过变量引用实现,例如将函数赋值给对象属性后通过该属性调用。


function test() {
 console.log(this.name);
}
var obj = {
 name: 'qiutc',
 foo: test
}
obj.foo();
// 'qiutc'

当使用call或apply方法时,this的指向会被强制改变为指定的第一个参数。


 function a(a,b,c) {
    console.log(this.name);
    console.log(a,b,c)
  }
  const b = {
    name: "segmentFault"
  }
  a.call(b,1,2,3)    
  //输出 segmentFault和 1,2,3
  function a(a,b,c) {
    console.log(this.name);
    console.log(a,b,c)
  }
  a.apply(b,[1,2,3])
  //输出segmentFault和1,2,3

而bind方法的行为则有所不同:


  function a() {
    console.log(this.name);
  }
  const b = {
    name: "segmentFault"
  }
  a.bind(b, 1, 2, 3)

此时控制台不会立即输出任何结果,因为bind并不会直接执行原函数,而是返回一个新的函数。这个新函数的this指向被绑定为bind的第一个参数。


  function a() {
    console.log(this.name);
  }
  const b = {
    name: "segmentFault"
  }
  const c = a.bind(b, 1, 2, 3)
  c()
  //此时输出segmentFault

手写实现call与apply方法

接下来,我们开始手写实现call方法。

首先,我们在Function.prototype上定义一个名为myCall的方法。


 Function.prototype.myCall = function (context, ...arg) {
    const fn = Symbol('临时属性')
    context[fn] = this
    context[fn](...arg)
    delete context[fn]
  }

短短四行代码即可实现call的基本功能,其核心思路如下:

  • 通过将函数作为对象的属性进行调用,利用对象属性访问机制使函数内部的this指向该对象。
  • 每次调用时,动态生成一个唯一的Symbol属性名,调用完成后立即删除该属性,以保持对象干净。
  • 这个Symbol属性名对应的值就是当前正在调用myCall的原函数。
  • 在函数形参中使用...arg(剩余参数语法)可以将所有传入的参数收集到一个数组中,在函数内部通过该变量即可访问所有参数。
  • 在调用context[fn](...arg)时,...arg用于展开数组,将数组元素作为独立参数依次传入函数。

为了清晰展示原理,本文暂不进行类型判断和边界错误处理,仅聚焦于核心逻辑的讲解。

随后,我们来实现apply方法。

同样在Function.prototype上定义myApply方法。


//实现自己的myApply
  Function.prototype.myApply = function (context, arg) {
    const fn = Symbol('临时属性')
    context[fn] = this
    context[fn](...arg)
    delete context[fn]
  }
  const obj2 = {
    a: 1
  }
  test.myApply(obj2, [2, 3, 4])

其实现逻辑与call类似,区别在于apply接收的第二个参数是一个数组。因此,在调用时只需使用...操作符将该数组展开即可传入函数。

深入解析与实现bind方法

接下来处理bind方法的实现。

bind与call、apply的本质区别在于:bind不会立即改变原函数的this指向并调用,而是返回一个新函数(该函数内部已绑定指定的this),且不会执行原函数;而call和apply会直接改变this并立即调用原函数。

bind在编写框架源码(如Koa等)时应用广泛。


 //实现自己的myBind
  Function.prototype.myBind = function (context, ...firstarg) {
    const that = this
    const bindFn = function (...secoundarg) {
      return that.myCall(context, ...firstarg, ...secoundarg)
    }
    bindFn.prototype = Object.create(that.prototype)
    return bindFn
  }

  var fnbind = test.myBind(obj, 2)
  fnbind(3)

同理,我们在原型上定义myBind方法。

这里需要保存最初调用myBind时的函数上下文(this劫持)。

返回一个新的函数,该新函数内部通过调用我们实现的myCall方法,确保this指向已确定,并返回新函数。

学习应循序渐进,建议按照本文顺序自行封装一遍。当然,完整的bind实现还需判断是否通过new关键字调用,以处理原型链继承的情况。

以下是完整版本的bind实现代码。


Function.prototype.myBind = function (objThis, ...params) {
  const thisFn = this; // 存储源函数以及上方的params(函数参数)
  // 对返回的函数 secondParams 二次传参
  let fToBind = function (...secondParams) {
    console.log('secondParams',secondParams,...secondParams)
    const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
    const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
    return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
  };
  fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
  return fToBind; // 返回拷贝的函数
};

以上就是本文的全部内容,希望这些解析能对您的学习有所帮助,也欢迎大家继续支持本站。

作者最新文章