一起手写call和apply

查询侠
• 阅读 963

1. 什么是call和apply

传送门:理解与使用js中的apply()和call()
至于为什么要手写代码,不仅仅是为了面试,也是为了帮助我们更好的理解代码逻辑,深入其中原理最好的方法。

2. 手写call

写个call方法,跟写一个项目是一样的。都要先分析其需求的功能点,需求分析要到位,再去逐个击破。
首先,我们需要知道call是函数原型上的一个方法,它的作用是绑定对象和参数,并执行函数。使用方法如下:

function.call(thisObj, arg1, arg2, ...)

开始实现:

  1. 我们就给我们手写的call方法叫myCall,接受的第一个参数 thisObj 是目标函数执行时的目标对象的值,从第二个可选参数arg1开始的其他参数,将作为目标函数执行时的实参。
  2. 需要分析call函数都有哪些‘需求’

    • 需要判断是否严格模式(this作用域的指向问题,非严格模式需要对thisObj特殊处理)
    • 如何判断严格模式
    • 如果thisObj不是对象类型怎么处理,因为我们是劫持绑定对象

代码如下:

// 首先apply是原型链Function.prototype上的一个方法
Function.prototype.myCall = function() {
  // 通过arugments拿到所有参数
  // 第一个参数是绑定的this对象
  var thisObj = arguments[0];
  // 判断是否严格模式
  var isStrictMode = (function(){return this === undefined}())
  if (!isStrictMode) {
    // 如果在非严格模式下,thisObj的值是null或undefined,需要将thisObj置为全局对象
    if (thisObj === null || thisObj === undefined) {
      // 获取全局对象时兼顾浏览器环境和Node环境
      thisObj = (function(){return this}())
    } else {
      // 非对象类型,需要转换类型
      var tthisObjType = typeof thisObj
      if (thisObjType === 'number') {
       thisObj = new Number(thisObj)
      } else if (thisObjType === 'string') {
        thisObj = new String(thisObj)
      } else if (thisObjType === 'boolean') {
        thisObj = new Boolean(thisObj)
      }
    }
  }
  // 从索引1开始的剩余参数
  var invokeParams = [...arguments].slice(1);
  // 接下来要调用目标函数,那么如何获取到目标函数呢?
  // 实际上this就是目标函数,因为myCall是作为一个方法被调用的,this当然指向调用对象,而这个对象就是目标函数
  // 这里做这么一个赋值过程,是为了让语义更清晰一点
  var invokeFunc = this;
  // 此时如果thisObj对象仍然是null或undefined,那么说明是在严格模式下,并且没有指定第一个参数或者第一个参数的值本身就是null或undefined,此时将目标函数当成普通函数执行并返回其结果即可
  if (thisObj === null || thisObj === undefined) {
    return invokeFunc(...invokeParams)
  }
  // 否则,让目标函数成为thisObj对象的成员方法,然后调用它
  // 直观上来看,可以直接把目标函数赋值给对象属性,为了key值唯一,防止覆盖掉thisObj对象的原有属性,可以创建一个唯一的属性名,使用Symbol处理
  var symbolPropName = Symbol(thisObj)
  thisObj[symbolPropName] = invokeFunc
  // 返回目标函数执行的结果
  return thisObj[symbolPropName](...invokeParams)
}

上代码测试一下:

Math.max.myCall(null, 1,2,3,4,5)
// 5

function mycallTest(a, b) {
  var args = [].slice.myCall(arguments)
  console.log(arguments, args)
}
mycallTest(1, 2)

var obj = {
  name: 'jack'
};
var name = 'ross';
function getName() {
  return this.name;
}
getName();
getName.myCall(obj);

可以看到,效果是有的。但是毕竟是手写的,跟源码还是有区别的,考虑情况可能会有不到位的地方,会有bug场景。

3. 手写apply

当我们写了call之后,apply就方便许多了,很多逻辑基本一致。我们只需要注意两个方法的差异点:第二个参数为数组,下面就不写注释了,直接上代码:

Function.prototype.myApply = function(thisObj, params) {
  var isStrict = (function(){return this === undefined}())
  if (!isStrict) {
    var thisObjType = typeof thisObj
    if (thisObjType === 'number') {
     thisObj = new Number(thisObj)
    } else if (thisObjType === 'string') {
      thisObj = new String(thisObj)
    } else if (thisObjType === 'boolean') {
      thisObj = new Boolean(thisObj)
    }
  }
  var invokeFunc = this;
  // 处理第二个参数
  var invokeParams = Array.isArray(params) ? params : [];
  if (thisObj === null || thisObj === undefined) {
    return invokeFunc(...invokeParams)
  }
  var symbolPropName = Symbol(thisObj)
  thisObj[symbolPropName] = invokeFunc
  return thisObj[symbolPropName](...invokeParams)
}

简单测试一下:

Math.max.myApply(null, [1, 2, 4, 8])
// 8

OK,大功告成!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
巴拉米 巴拉米
4年前
bind、call、apply 区别?如何实现一个bind?
一、作用call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向那么什么情况下需要改变this的指向呢?下面举个例子var name"lucy";const obj{    name:"martin",    say:function (){        co
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
小嫌 小嫌
3年前
bind()与call()和apply()之间的区别
具体内容以及示例可参见网站:需要记住的基本规则1."this"指代一个对象2."this"指的是调用它包含的函数的对象。3.在全局上下文中,“this”指的是窗口对象,或者如果使用“严格模式”则是未定义的。varcarregistrationNumber:"GA12345",brand:"Toyota",dis
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
MySQL数据库InnoDB存储引擎Log漫游(1)
作者:宋利兵来源:MySQL代码研究(mysqlcode)0、导读本文介绍了InnoDB引擎如何利用UndoLog和RedoLog来保证事务的原子性、持久性原理,以及InnoDB引擎实现UndoLog和RedoLog的基本思路。00–UndoLogUndoLog是为了实现事务的原子性,
Stella981 Stella981
3年前
JavaScript中call()与apply()有什么区别?
今天读《JavaScript权威指南》时发现其中有段代码用到了apply方法用于递归实现数组的展开。可是我不懂这个函数的用法,因此查了一下,将资料整理如下。Javascript的每个Function对象中有一个apply方法:function.apply(thisObj,argArray)还有一个类似功能的call方法:
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这