likes
comments
collection
share

深入理解JavaScript中的call方法及其自定义实现

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

大家好,我是墩墩大魔王丶。在编写JavaScript代码时,我们常常需要处理函数的执行上下文。call方法是一个非常有用的工具,它允许我们在调用函数时灵活地指定this的值,并且还能传入其他参数。但是,你是否曾思考过它的原理和实现方式呢?今天,让我们一同深入研究JavaScript中的call方法,探索其中蕴含的核心原理和巧妙之处!🐹

JavaScript 中的 call 方法是用于调用函数, 并且可以在调用时指定函数内部的 this 关键字的值, 同时还可以用参数列表的形式传入其他参数。

实现思路分析:

  1. Function 构造函数的原型 prototype 上 定义属性 myCall

  2. 属性 myCall 是一个函数, 函数参数包括 context 对象( 指定 this 的值) 和 一个参数序列 ...args

  3. 编写函数逻辑

    1. 判断 context 是否为空或undefined

      1. 如果是 context 赋值为 window
      2. 如果不是 context = Object(context) ①
    2. 定义变量 key = Symbol() ②

    3. 将 当前函数 设置为 context 的方法 context[key] = this ③

    4. context 调用该方法, 此时函数的 this 指向 context: let result = context[key] ( ...args )

    5. 使用 delete 销毁 context 的方法 ④

    6. 返回运行结果 result

编码

Function.prototype.myCall = function(context, ...args){
    if(context === null || context === undefined){
        context = window
    } else {
        context = Object(context)
    }
    const key = Symbol()
    context[key] = this
    const result = context[key](...args)
    delete context[key]
    return result
}

① 在给定的函数中,context 参数表示调用时所指定的上下文对象,它可能是一个原始值(如null、undefined)或者已经是一个对象。为了确保在后续代码中能够正常使用context作为对象,而不管它是什么类型,使用Object()进行封箱是一个常见的做法。这样做可以确保context变量始终是一个对象,并且可以调用对象的方法。

② 创建了一个全局唯一的 Symbol, 它被用作一个键,用来在上下文对象中存储函数引用,以确保不会与对象的其他属性冲突。因为 Symbol 值是唯一的,所以可以确保在对象中使用 Symbol 作为键时不会与其他属性冲突。这样做可以避免意外覆盖或污染上下文对象的其他属性。

③ 此处的 this 是调用 myCall 的函数, 如 func.myCall(null, 1), this 是 func 或 Object.prototype.toString.myCall({})。

delete 运算符允许动态地删除对象的属性或数组的元素,使得在运行时可以根据需要更改对象或数组的结构。

特点1: 在数据中删除属性,不影响数组长度: 删除数组的元素时,delete 只是将指定位置的元素设置为 undefined,并不会改变数组的长度。这对于需要保持数组长度不变,但又想删除某些元素的情况很有用。

特点2: 在对象中删除属性: 可以使用 delete 删除对象中的属性,这对于在不需要某个属性时清理对象很有帮助。

扩展

delete 的优缺点

JavaScript 中的 delete 运算符用于删除对象的属性或数组的元素。

优势

  1. 灵活性: delete 运算符允许动态地删除对象的属性或数组的元素,使得在运行时可以根据需要更改对象或数组的结构。
  2. 不影响数组长度: 在删除数组的元素时,delete 只是将指定位置的元素设置为 undefined,并不会改变数组的长度。这对于需要保持数组长度不变,但又想删除某些元素的情况很有用。
  3. 在对象中删除属性: 可以使用 delete 删除对象中的属性,这对于在不需要某个属性时清理对象很有帮助。

缺陷

  1. 性能影响:在删除属性或数组元素时,delete操作会导致性能下降,特别是在大型对象或数组上的操作。这是因为delete操作可能会导致对象的内部重新排列,而且它可能不会释放对象所占用的内存。
  2. 不可撤销:一旦使用delete删除了对象的属性或数组的元素,就无法恢复它们。这可能会导致不可预测的结果或数据丢失。
  3. 影响性能的原因:删除对象的属性可能会导致对象的内部结构发生变化,从而影响JavaScript引擎的优化和性能。在某些情况下,可以通过设置属性值为null或undefined来模拟删除,而不实际删除属性。

针对 delete 操作的缺点,可以考虑以下解决方案:

  1. 使用数组的splice方法:在需要删除数组元素的情况下,可以使用数组的splice方法。该方法会直接修改数组,删除指定位置的元素,并且不会留下空的占位符。这样可以避免使用delete操作导致的性能问题和不可撤销的影响。
  2. 使用null或undefined标记删除:可以考虑将对象属性或数组元素的值设置为null或undefined,而不是使用delete进行删除。这样做不会改变对象或数组的结构,避免了delete操作可能导致的性能问题,并且可以更容易地恢复被删除的数据。
  3. 使用新的数据结构:如果对性能要求较高,可以考虑使用Map、Set等新的数据结构来代替对象和数组。这些数据结构在添加、删除元素时的性能通常比对象和数组更好,并且提供了更多灵活性和功能。

call、apply、bind 的异同

三者都是用于函数调用并指定 this 。

callapplybind 方法都可以用于改变函数的执行上下文(this 值),但它们在使用方式和作用上有一些区别。

  1. 执行方式不同: call 和 apply 立即执行, bind 返回一个新的绑定函数, 需要再次调用才会执行 (延迟执行)
  2. 返回值不同: 和上面有些类似, call 和 apply 返回被调用函数的执行结果; bind 返回新的函数。
  3. 传参不同: call 是通过参数列表接收参数、apply 是通过数组接收参数、bind 调用绑定后的函数单独传参
  4. 兼容性: bind方法是ES5后才开始提供。

结语

希望这篇文章能帮助大家更好地理解 call 方法的实现,欢迎大家多多交流,共同进步!💐

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