• javascript复习之旅 9.1 从0到1认识`call apply`


    start

    • 番茄我初次学习call apply 这个两个方法 , 由于对 this 没有理解透彻,导致学习这两个方法的时候,感觉到异常吃力。
    • 所以强烈建议学习本文之前,先去弄懂 执行上下文 && this

    开始

    好了废话不多说,上来先了解一下这两个函数有什么用。

    call

    1. call 是什么?

    先说call, 官方文档

    image.png

    官方文档很简洁,但是我们一步一步分析。

    第一:call 方法,是在函数的原型上的。所以我收到的第一个信息

    1. 默认情况下只有函数才能 .call()

    第二:官方的解释是: call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 所以又收到了这么几个信息

    1. 可以指定 this
    2. 给出一个或多个参数
    3. 调用函数

    2. call 有什么用

    call 可以使用一个指定的 this 来调用一个函数。 用通俗易懂的话来描述 就是改变 this 指向并且调用函数。

    示例一:默认的this指向

    // 1. 定义一个函数
    function tomato() {
      // 2. 打印这个函数执行的时候的 this
      console.log('我是番茄', this)
    }
    
    // 3.直接调用 this指向全局对象 window
    tomato()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1656405930979.jpg

    示例二:使用call修改this指向为自定义的对象

    // 1. 定义一个函数
    function tomato() {
      // 2. 打印这个函数执行的时候的 this
      console.log('我是番茄', this)
    }
    
    // 3.定义一个对象 obj
    var obj = {
      name: '我是obj',
    }
    
    // 4.使用call,修改 this 指向为 obj
    tomato.call(obj)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1656405920091.jpg

    示例三:使用call修改this指向为自定义的对象,并且传递参数

    // 1. 定义一个函数
    function tomato() {
      // 2. 打印这个函数执行的时候的 this
      console.log('我是番茄', this, '打印一下我接受到的参数', ...arguments)
    }
    
    // 3.定义一个对象 obj
    var obj = {
      name: '我是obj',
    }
    
    // 4.使用call,修改 this 指向为 obj
    tomato.call(obj, 1, 2, 3, 4, 5, 6)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1656406121709.jpg

    3. 手写一个 call 加深理解

    第一版 基本功能实现

    // 1. 既然 call 是方法 ,那我们也定义一个名称为 myCall 的函数,挂载在Function的原型上
    Function.prototype.myCall = function (content) {
      // 2. 函数接收的第一个参数是需要修改 this 指向的内容,所以我定义一个形参content
      /**
       * 3.我们这里模拟call怎么去修改this的指向?
       *   修改this指向无非4种方式
       *   3.1  默认指向全局对象(非严格模式);undefined(严格模式)        =>默认的 pass
       *   3.2  谁调用,this就指向谁  采用它
       *   3.3  call apply bind  =>我们就是在模拟这个方法  pass
       *   3.4  new
       */
    
      // 4.函数myCall的this指向谁?  谁调用,指向谁,例如 tomato.myCall()  这里this就指向 函数tomato
      // console.log('this', this)
    
      // 5.所以通过 content 去调用 tomato 即可
      content.__fn__ = this
      content.__fn__()
      delete content.__fn__
      /*
        ①
        let content = {
          __fn__: function tomato() {
            console.log('我是番茄')
          }
        }
        ②
         content.__fn__()
        ③
         delete content.__fn__
      */
    }
    
    let obj = {
      name: '我是obj',
    }
    
    function tomato() {
      console.log('我是番茄', this)
    }
    
    tomato.myCall(obj)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    第二版 去除掉注释

    Function.prototype.myCall = function (content) {
      content.__fn__ = this
      content.__fn__()
      delete content.__fn__
    }
    
    let obj = {
      name: '我是obj',
    }
    
    function tomato() {
      console.log('我是番茄', this)
    }
    
    tomato.myCall(obj)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    好了测试一下,看是否 ok

    1656408965742.jpg

    第三版 支持接受额外参数

    Function.prototype.myCall = function (content) {
      // 处理未知的参数肯定使用 arguments ,这里要注意两点:1. arguments 是类数组,不支持数组某些方法; 2.当然可以用es6的解构,这里为了兼容低版本我们就使用for循环
    
      var arr = []
      for (var index = 1; index < arguments.length; index++) {
        arr.push('arguments[' + index + ']')
      }
      content.__fn__ = this
      eval('content.__fn__(' + arr + ')')
      delete content.__fn__
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    说明:

    1. 这里可以直接 es6 的解构
    2. 如果不想使用 es6 的语法,这里可是使用 for 循环去遍历 arguments
    3. 我最开始是直接用数组存储参数,然后使用 arr.join(',') 拆分, join() 方法将数组作为字符串返回。如果数组项中有对象,对象会被转换为 [object Object] 这种方式 不可取。
    4. eval() 可以简单理解为传入了一个字符串的函数 例如
    var str = "function say() {console.log('你好呀')};say()"
    eval(str)
    
    • 1
    • 2

    第四版 支持返回值; 支持传入 null

    Function.prototype.myCall = function (content) {
      content = content || window
      var arr = []
      for (var index = 1; index < arguments.length; index++) {
        arr.push('arguments[' + index + ']')
      }
      content.__fn__ = this
      var end = eval('content.__fn__(' + arr + ')')
      delete content.__fn__
    
      return end
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    手写 call 最终版

    最终版本 一

    Function.prototype.myCall = function (content) {
      content = content || window
      var arr = []
      for (var index = 1; index < arguments.length; index++) {
        arr.push('arguments[' + index + ']')
      }
      content.__fn__ = this
      var end = eval('content.__fn__(' + arr + ')')
      delete content.__fn__
    
      return end
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最终版本 二

    Function.prototype.myCall = function (content) {
      content = Object(content) || window
      let arr = [...arguments].slice(1)
    
      let symbolFn = Symbol('fn')
      content[symbolFn] = this
      let end = content[symbolFn](...arr)
      delete content[symbolFn]
      return end
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    apply

    apply 和 call 功能非常相似了,它和 call 的区别就是,只是它的传参是以一个数组的形式,而 call 是多个参数

    apply 的手写实现

    Function.prototype.apply = function (context, arr) {
      context = Object(context) || window
      context.fn = this
    
      var result
      if (!arr) {
        result = context.fn()
      } else {
        var args = []
        for (var i = 0, len = arr.length; i < len; i++) {
          args.push('arr[' + i + ']')
        }
        result = eval('context.fn(' + args + ')')
      }
    
      delete context.fn
      return result
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    call apply 常用的场景

    1. 类数组转换成数组
    function tomato() {
      let arr = Array.prototype.slice.call(arguments)
      arr.push(1)
      console.log(arr)
    }
    
    tomato(1, 2, 3, 4, 5)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.类型判断

    Object.prototype.toString.call('123')
    // '[object String]'
    
    • 1
    • 2

    参考博客

    JavaScript 深入之 call 和 apply 的模拟实现

    end

    • ღ( ´・ᴗ・` )比心
  • 相关阅读:
    【LeetCode 1758】生成交替二进制字符串的最少操作数
    【多线程】线程安全(重点)
    克亚营销铁律12条
    怎么用测量来分辨双向TVS管的好坏?-优恩
    JavaScript 布尔类型(boolean) 和为定义类型(undefined)
    链表不得不刷的进阶题目,牛客TOP101链表相关——链表刷题
    谁还说我没表情包用?马上用Python采集上万张个表情包
    Android前端架构设计:基于观察者模式的架构设计(欢迎各路高手点评)
    最大公约数的四种方法
    4.2 metasploit 开发 exploit
  • 原文地址:https://blog.csdn.net/wswq2505655377/article/details/125509035