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()

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)

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)

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)

第二版 去除掉注释

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)

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

第四版 支持返回值; 支持传入 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
}
手写 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
}

最终版本 二

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
}

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
}

call apply 常用的场景

  1. 类数组转换成数组
function tomato() {
  let arr = Array.prototype.slice.call(arguments)
  arr.push(1)
  console.log(arr)
}

tomato(1, 2, 3, 4, 5)

2.类型判断

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

参考博客

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

end

  • ღ( ´・ᴗ・` )比心
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lazy_tomato

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值