likes
comments
collection
share

js手写 call、apply、bind 函数

作者站长头像
站长
· 阅读数 77

了解 call、apply、bind 函数

call、apply和bind三个函数都可以用来改变JavaScript函数内部的this指向,这是它们共同的特点,只不过在用法上略微不同。过多的话也不多说直接上案例吧

call

call是Function对象自带的一个方法,它可以用来调用函数并且改变函数内部的this指向。call函数的第一个参数就是要指定的this指向,后面的参数则是传入函数的参数列表。

function sayHello() {
  console.log(this);  // { name: 'John' }
  console.log(`Hello, ${this.name}!`);
}

let person = { name: 'John' };
sayHello.call(person); // 输出:Hello, John!

在这个例子中,我们使用了call函数将sayHello函数内部的this指向了person对象,从而输出了“Hello, John!”这句话。

apply

apply与call很相似,区别在于传入的参数列表是一个数组,而不是一系列单独的参数。这使得apply更适合用于传入动态数量的参数。

function sum(a, b, c) {
  console.log(this);  // { name: 'zjl' }
  return a + b + c;
}

const obj = {
  name: 'zjl'
}
let nums = [1, 2, 3];
let result = sum.apply(obj, nums);
console.log(result); // 输出:6

在这个例子中,我们使用了apply函数将sum函数内部的this指向了obj对象,并且传入了一个数组nums作为参数列表,最终计算出了1+2+3=6。

bind

bind函数也可以用来改变函数内部的this指向,但是它与call和apply的区别在于,bind函数会返回一个新函数,而不是立即调用原函数并改变this指向。

let person = { name: 'John' };

function sum(num1, num2) {
  console.log(this);  // { name: 'John' }
  return num1 + num2
}

let res = sum.bind(person, 1);
console.log(res(2));  // 3

bind函数是这三个函数当中较为特别的,它可以实现柯里化操作,我们会发现在我们这个例子中 sum 函数接受两个 number 参数,第一次我们通过 bind 函数调用的时候只传递了一个参数,第二次我们调用通过调用 bind 函数返回的函数又传递了一个参数,输出的结果就是先后传递number参数的和,这不就是js中的柯里化操作么

关于这三个函数的介绍和应用就到这里了,接下来就让我来带大家实现用js手写这个三个函数

手写 call 函数

步骤:

  1. 在Function原型上添加一个mycall方法,该方法接收两个参数:thisArg和args,其中thisArg表示需要改变的this指向,args表示传递给函数的参数。
  2. 如果传入的thisArg为null或undefined,则将其转换为全局对象window。
  3. 创建一个Symbol作为属性名,避免与对象原有属性名冲突。
  4. 将当前函数作为thisArg对象的属性。
  5. 将fn属性设置为不可枚举。
  6. 调用thisArg对象的属性,即当前函数,并传入参数args。
  7. 删除thisArg对象的属性。
  8. 返回函数调用结果。
Function.prototype.mycall = function (thisArg, ...args) {
  // 如果传入的thisArg为null或undefined,则将其转换为全局对象window
  thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window

  // 创建一个Symbol作为属性名,避免与对象原有属性名冲突
  const fn = Symbol('fn')
  // 将当前函数作为thisArg对象的属性
  thisArg[fn] = this

  // 将fn属性设置为不可枚举
  Object.defineProperty(thisArg, fn, {
    enumerable: false
  })

  // 调用thisArg对象的属性,即当前函数,并传入参数args
  const res = thisArg[fn](...args)
  // 删除thisArg对象的属性
  delete thisArg[fn]

  // 返回函数调用结果
  return res
}

function test(num1, num2) {
  console.log(this);  // { name: 'zjl' }
  
  console.log(num1 + num2);  // 3
}
const obj = {
  name: 'zjl'
}

// 调用test函数,并将obj作为thisArg,1和2作为参数
test.mycall(obj, 1, 2)

手写 apply 函数

手写 apply 函数跟手写 call 函数很像,不同之处就是 apply 函数传入的参数列表是一个数组,而不是一系列单独的参数。废话不多说,直接上代码:

Function.prototype.myapply = function (thisArg, argsArr) {
  // 如果传入的thisArg为null或undefined,则将其转换为全局对象window
  thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window

  // 创建一个Symbol作为属性名,避免与对象原有属性名冲突
  const fn = Symbol('fn')
  // 将当前函数作为thisArg对象的属性
  thisArg[fn] = this

  // 将fn属性设置为不可枚举
  Object.defineProperty(thisArg, fn, {
    enumerable: false
  })

  // 调用thisArg对象的属性,即当前函数,并传入参数args
  const res = thisArg[fn](...argsArr)
  // 删除thisArg对象的属性
  delete thisArg[fn]

  // 返回函数调用结果
  return res
}

function sum(num1, num2) {  // 定义一个示例函数
  console.log(this);  // { name: 'jay' }

  return num1 + num2;  // 返回两个参数的和
}
const obj = { name: "jay" };  // 定义一个对象作为thisArg

const res = sum.myapply(obj, [2, 3])  // 使用obj作为this和[2, 3]作为参数调用sum函数
console.log(res);  // 5

手写 bind 函数

步骤:

  1. 在Function原型上添加一个mybind方法,该方法接收两个参数:thisArg和args,其中thisArg表示需要改变的this指向,args表示传递给函数的参数。
  2. 如果传入的thisArg为null或undefined,则将其转换为全局对象window。
  3. 将 this 赋值给 _this。
  4. 返回一个新的函数 proxyFn,接收 proxyArgs 作为参数。
  5. 在 proxyFn 函数内部创建一个Symbol作为属性名,避免与对象原有属性名冲突。
  6. 将原函数作为thisArg对象的属性。
  7. 将fn属性设置为不可枚举。
  8. 调用thisArg对象的属性,即原函数,并传入参数args和proxyArgs。
  9. 删除thisArg对象的属性。
  10. 返回 proxyFn 函数调用结果。
Function.prototype.mybind = function(thisArg, ...args) {
  // 如果传入的thisArg为null或undefined,则将其转换为全局对象window
  thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window

  const _this = this

  // 返回一个新的函数,接收proxyArgs作为参数
  function proxyFn(...proxyArgs) {
    // 创建一个Symbol作为属性名,避免与对象原有属性名冲突
    const fn = Symbol('fn')
    // 将原函数赋值给thisArg的fn属性
    thisArg[fn] = _this

    // 将fn属性设置为不可枚举
    Object.defineProperty(thisArg, fn, {
      enumerable: false
    })

    // 合并两个剩余参数
    const argsArr = [...args, ...proxyArgs]
    // 调用原函数并返回结果
    const res = thisArg[fn](...argsArr)
    // 删除fn属性
    delete thisArg[fn]

    // 返回函数调用结果
    return res
  }

  return proxyFn
}

function sum(num1, num2) {
  console.log(this);  // { name: 'jay' }

  return num1 + num2
}
const obj = { name: 'jay' }

// 使用mybind方法将sum函数绑定到obj对象上,并传入参数1
const proxyFn = sum.mybind(obj, 1)
// 调用proxyFn函数并传入参数2
console.log(proxyFn(2));  // 3

总结

至此我们就已经完成了如何用js手写 call、apply、bind 函数,其实不难发现整个代码实现其实不难,无非就是思路问题,主要看自己是否敢去尝试,共勉!

转载自:https://juejin.cn/post/7227453567685427260
评论
请登录