使用JS简单实现一下apply、call和bind方法

1.方法介绍

apply、call和bind都是系统提供给我们的内置方法,每个函数都可以使用这三种方法,是因为apply、call和bind都实现在了Function的原型上(Function.prototype),而他们的作用都是给我们函数调用时显式绑定上this。下面先介绍一下它们的基本用法:

  • apply方法:调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

    • 使用语法:func.apply(thisArg, [argsArray])

      • thisArg:在func函数调用时绑定的this值;
      • [argsArray]:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func函数;
    • 使用效果:

      function foo(x, y ,z) {
      console.log(this, x, y, z)
      } const obj = { name: 'curry', age: 30 }
      /**
      * 1.将obj对象绑定给foo函数的this
      * 2.数组中的1 2 3分别传递给foo函数对应的三个参数
      */
      foo.apply(obj, [1, 2, 3])

  • call方法:使用一个指定的 this值和单独给出的一个或多个参数来调用一个函数。

    • 使用语法:func.call(thisArg, arg1, arg2, ...)

      • thisArg:在func函数调用时绑定的this值;
      • arg1, arg2, ...:指定的参数列表,将作为参数传递给func函数;
    • 使用效果:

      function foo(x, y ,z) {
      console.log(this, x, y, z)
      } const obj = { name: 'curry', age: 30 }
      /**
      * 1.将obj对象绑定给foo函数的this
      * 2.call剩余参数中的a b c分别传递给foo函数对应的三个参数
      */
      foo.call(obj, 'a', 'b', 'c')

  • bind方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

    • 使用语法:func.bind(thisArg[, arg1[, arg2[, ...]]])

      • thisArg:调用func函数时作为this参数传递给目标函数的值;
      • arg1, arg2, ...:当目标函数被调用时,被预置入func函数的参数列表中的参数;
    • 使用效果:

      function foo(...args) {
      console.log(this, ...args)
      } const obj = { name: 'curry', age: 30 }
      /**
      * 1.将obj对象绑定给foo函数的this
      * 2.bind剩余参数中的1 2 3分别传递给foo函数中参数
      * 3.也可在newFoo调用时传入参数,这时bind传递的参数会与newFoo调用时传递的参数进行合并
      */
      const newFoo = foo.bind(obj, 1, 2, 3)
      newFoo()
      newFoo('a', 'b', 'c')

总结:

  • apply和call主要用于在函数调用时给函数的this绑定对应的值,两者作用类似,主要区别就是除了第一个参数,apply方法接受的是一个参数数组,而call方法接受的是参数列表。
  • bind也是给函数指定this所绑定的值,不同于apply和call的是,它会返回一个新的函数,新函数中的this指向就是我们所指定的值,且分别传入的参数会进行合并。

2.apply、call和bind方法的实现

为了所有定义的函数能够使用我们自定义的apply、call和bind方法,所以需要将自己实现的方法挂在Function的原型上,这样所有的函数就可以通过原型链找到自定义的这三个方法了。

2.1.apply的实现

Function.prototype.myApply = function(thisArg, argArray) {
// 1.获取当前需要被执行的函数
// 因为myApply是需要被当前函数进行调用的,根据this的隐式绑定,此处的this就是指向当前需要被执行的函数
const fn = this // 2.对传入的thisArg进行边界判断
if (thisArg === null || thisArg === undefined) {
// 当传入的是null或者undefined是,被执行函数的this直接指向全局window
thisArg = window
} else {
// 将传入的thisArg对象化,方便后面在thisArg添加属性
thisArg = Object(thisArg)
}
// 也可简单写成三元运算符:
// thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg) // 3.将获取的fn添加到thisArg对象上
// 这里使用Symbol的原因是避免外部传入的thisArg中的属性与添加fn有冲突
const fnSymbol = Symbol()
Object.defineProperty(thisArg, fnSymbol, {
enumerable: false,
configurable: true,
writable: false,
value: fn
})
// 也可简单写成
// thisArg[fnSymbol] = fn // 4.对argArray进行判断
// 看是否有传入值,没有值传入就默认 []
argArray = argArray || [] // 5.调用获取的fn函数,并将对应传入的数组展开传递过去
const result = thisArg[fnSymbol](...argArray)
// 调用完后删除添加的属性
delete thisArg[fnSymbol] // 6.将结果返回
return result
}

测试:虽然打印出来的对象中还存在Symbol属性,实际上已经通过delete删除了,这里是对象引用的问题。

function foo(x, y, z) {
console.log(this, x, y, z)
} foo.myApply({name: 'curry'}, [1, 2, 3])

2.2.call的实现

call方法的实现和apply方法的实现差不多,主要在于后面参数的处理。

Function.prototype.myCall = function(thisArg, ...args) {
// 1.获取当前需要被执行的函数
const fn = this // 2.对传入的thisArg进行边界判断
thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg) // 3.将获取的fn添加到thisArg对象上
const fnSymbol = Symbol()
thisArg[fnSymbol] = fn // 4.调用获取的fn函数,并将对应传入的args传递过去
const result = thisArg[fnSymbol](...args)
// 调用完后删除添加的属性
delete thisArg[fnSymbol] // 5.将结果返回
return result
}

测试:

function foo(x, y, z) {
console.log(this, x, y, z)
} foo.myCall({name: 'curry'}, 1, 2, 3)

2.3.bind的实现

bind方法的实现稍微复杂一点,需要考虑到参数合并的问题。

Function.prototype.myBind = function(thisArg, ...argsArray) {
// 1.获取当前的目标函数,也就是当前使用myBind方法的函数
const fn = this // 2.对传入的thisArg进行边界判断
thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg) // 3.将获取的fn添加到thisArg对象上
const fnSymbol = Symbol()
thisArg[fnSymbol] = fn // 4.定义一个新的函数
function newFn(...args) {
// 4.1.合并myBind和newFn传入的参数
const allArgs = [...argsArray, ...args]
// 4.2.调用真正需要被调用的函数,并将合并后的参数传递过去
const result = thisArg[fnSymbol](...allArgs)
// 4.3.调用完后删除添加的属性
delete thisArg[fnSymbol] // 4.4.将结果返回
return result
} // 6.将新函数返回
return newFn
}

测试:

function foo(x, y, z) {
console.log(this, x, y, z)
} const newFoo = foo.myBind({ name: 'curry' }, 1, 2)
newFoo(3)

使用JS简单实现一下apply、call和bind方法的更多相关文章

  1. 彻底理解了call()方法,apply()方法和bind()方法

    javascript中的每一个作用域中都有一个this对象,它代表的是调用函数的对象.在全局作用域中,this代表的是全局对象(在web浏览器中指的是window).如果包含this的函数是一个对象的 ...

  2. JS中的call、apply、bind方法

    JS中的call.apply.bind方法 一.call()和apply()方法 1.方法定义 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]] ...

  3. JS中call、apply的用法说明

    JS Call()与Apply()的区别 ECMAScript规范给所有函数都定义了Call()与apply()两个方法,call与apply的第一个参数都是需要调用的函数对象,在函数体内这个参数就是 ...

  4. js中call、apply、bind那些事

    前言 回想起之前的一些面试,几乎每次都会问到一个js中关于call.apply.bind的问题,比如- 怎么利用call.apply来求一个数组中最大或者最小值 如何利用call.apply来做继承 ...

  5. 理解JS中的call、apply、bind方法(*****************************************************************)

    在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:app ...

  6. js中call、apply、bind那些事2

    前言 回想起之前的一些面试,几乎每次都会问到一个js中关于call.apply.bind的问题,比如… 怎么利用call.apply来求一个数组中最大或者最小值 如何利用call.apply来做继承 ...

  7. js 之 call 、 apply

    在学习js过程中怎么也绕不过用到call.apply方法,感觉都差不多,现在看看他们的用法,区别 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(conte ...

  8. js简单 图片版时钟,带翻转效果

    js简单 图片版时钟,带翻转效果 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"& ...

  9. js简单操作Cookie

    贴一段js简单操作Cookie的代码: //获取指定名称的cookie的值 function getCookie(objName) { var arrStr = document.cookie.spl ...

随机推荐

  1. Antd使用timePicker封装时间范围选择器(React hook版)

    antd中提供了是日期范围选择器及datepaicker封装日期范围选择器的示例,但是没有时间选择范围的组件,这里使用两个timePicker组合一个事件范围选择器,通过disabled属性限定时间可 ...

  2. MySQL数据库学习打卡 DAY2

    今天学习了MySQL的DML操作,完成了关于增删改查所有基本内容的学习.

  3. 带你十天轻松搞定 Go 微服务系列(一)

    本文开始,我们会出一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建(本文) 服务拆分 用户服务 产品服务 订单服务 支付服务 RPC 服务 Au ...

  4. 5.14-HTTP间通信

    1.社长社员通信WEBSOCKET WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输.但它跟 HTTP 没什么关系,它是一种基于 TCP 的一种独立实现. 以前客户端想知道 ...

  5. Natasha 4.0 探索之路系列(一) 概况

    Natasha 简介 Natasha 是一个基于 Roslyn 的动态编译类库, 它以极简的 API 完成了动态编译的大部分功能, 使用它可以在程序运行时编译出新的程序集. Natasha 允许开发人 ...

  6. 春节将至,喜庆的烟花安排上(js实现烟花)

    一年一度的春节即将来临,然后苦逼的我还在使劲的摸鱼,未能回家体验小时候路边放爆竹的快乐时光,所以只能在网上来实现这个小小的心愿了.烟花静态效果图如下: 为了大伙复制方便就不分开写,直接复制即可,具体实 ...

  7. Mybatis插件,能做的事情真的很多

    大家好,我是架构摆渡人.这是实践经验系列的第九篇文章,这个系列会给大家分享很多在实际工作中有用的经验,如果有收获,还请分享给更多的朋友. Mybatis是我们经常用的一款操作数据库的框架,它的插件机制 ...

  8. Go 转义字符及风格

    今天来学习一下Go 中的转义字符,源码注释,规范的代码风格以及标准库API 文档; Go 转义字符常用的转义字符有以下几个:1. \t: 表示一个制表符(tab), 通常可以使用它进行排版; 2. \ ...

  9. C++ 反汇编:关于Switch语句的优化措施

    流程控制语句是C语言中最基本的判断语句,通常我们可以使用IF来构建多分支结构,但同样可以使用Switch语句构建,Switch语句针对多分支的优化措施有4种形式,分别是,IF-ELSE优化,有序线性优 ...

  10. python网络爬虫-动态网页抓取(五)

    动态抓取的实例 在开始爬虫之前,我们需要了解一下Ajax(异步请求).它的价值在于在与后台进行少量的数据交换就可以使网页实现异步更新. 如果使用Ajax加载的动态网页抓取,有两种方法: 通过浏览器审查 ...