call、apply、bind实现原理

简介: 本文介绍了call、apply、bind的用法和他们各自的实现原理。

网络异常,图片无法展示
|

本文介绍了call、apply、bind的用法和他们各自的实现原理。


call


call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 即:可以改变当前函数的this指向;还会让当前函数执行。


用法


function fun() {
  console.log(this.name, arguments)
}
let obj = { name: 'clying' }
fun.call(obj, 'deng', 'deng')
// clying [Arguments] { '0': 'deng', '1': 'deng' }


实现


call和apply的实现,都是使用将函数放到字面量obj的某个属性中,使函数中的this指向obj这个字面量对象。


简单的实现版本:


Function.prototype.mycall = function (context) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  context.fn()
  delete context.fn
}


给函数原型添加mycall方法,创建一个上下文对象context,如果传入的对象不存在时,将指向全局window。通过给context添加fn属性,context的fn引用调用该方法的函数fun,并执行fun。执行完成之后删除该属性fn。


当中需要先获取传入的参数,那它变成字符串数组。 执行方法使用的是eval函数,再通过eval计算字符串,并执行其中代码,返回计算结果。


升级版:


给call中传入参数。


Function.prototype.mycall = function (context) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  let arr = []
  for (let i = 1; i < arguments.length; i++) {
    arr.push('argument[' + i + ']') //  ["arguments[1]", "arguments[2]"]
  }
  let r = eval('context.fn(' + arr + ')') // 执行函数fun,并传入参数
  delete context.fn
  return r
}

此外,也可以通过解构的语法来实现call。

Function.prototype.mycall = function (context, ...args) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  context.fn(...args)
  delete context.fn
}


如果想要能够多次调用call方法,可以将context.fn(...args)保存到变量中,最后返回即可。


Function.prototype.mycall = function (context, ...args) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  let r = context.fn(...args)
  delete context.fn
  return r
}


apply


与call方法类似,call方法接收的是一个参数列表,而apply方法接收的是一个包含多个参数的数组。


用法


将函数中的this指向传入的第一个参数,第二个参数为数组。

function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
fun.apply(obj, [22, 1])
// clying Arguments(2) [22, 1]


实现


自己实现一个apply方法myapply。实现方法与call类似,不过在接收参数时,可以使用一个args作为传入的第二个参数。直接判断如果未传入第二个参数,直接执行函数;否则使用eval执行函数。


Function.prototype.myapply = function (context, args) {
 context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  if(!args) return context.fn()
  let r = eval('context.fn('+args+')')
  delete context.fn
  return r
}


bind


bind() 方法创建一个新的函数,不自动执行,需要手动调用bind() 。这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。


用法


将obj绑定到fun函数的this上,函数fun可以使用obj内部的属性,和传入的变量。


function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
let b = fun.bind(obj,2)
b(3)
// clying Arguments(2) [2, 3]


此外,bind方法绑定的函数还可以new一个实例,不过此时的this会发生改变。

升级版-使用原型属性用法:


function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
fun.prototype.age = 23
let b = fun.bind(obj, 3)
let instance = new b(4)
console.log(instance.age);
//undefined Arguments(2) [3, 4]
// 23


实现


基本版:


bind的实现可以基于call和apply的基础上实现。

因为bind不是立即执行的,所以可以通过返回一个函数,让用户手动执行。在返回函数中利用call或者apply传入指定的this对象和参数。


apply实现bind

Function.prototype.mybind = function (context) {
  let that = this
  let bindargs = Array.prototype.slice.call(arguments, 1)
  return function () {
    let args = Array.prototype.slice.call(arguments)
    return that.apply(context, bindargs.concat(args))
  }
}


利用apply方法,主要是在获取处理bind传入的参数,以及用户执行函数传入的参数。利用Array原型方法的slice方法,截取所需的参数。


在获取bind传入的参数时,需要从第二个参数开始截取,所以开始位置为1。


call实现bind


Function.prototype.mybind = function (context, ...args1) {
  let that = this
  return function (...args2) {
    return that.call(context, ...args1, ...args2)
  }
}


call实现直接将参数拼接call方法的后面即可。


升级版:


bind除了可以改变this指向、用户可以在bind后面传入参数也可以在用户执行时传入参数外。还可以让执行函数进行new操作。

当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。


apply

Function.prototype.mybind = function (context) {
  let that = this
  let bindargs = Array.prototype.slice.call(arguments, 1)
  function fBind() {
    let args = Array.prototype.slice.call(arguments)
    // 如果使用的是new,那么this会指向fBind实例,this作为当前实例传入 不是的话,使用context上下文对象
    return that.apply(this instanceof fBind ? this : context, bindargs.concat(args))
  }
  return fBind
}


在使用new操作符时,注意的是需要改变this的指向问题,如果是new,那么this指向的是实例,不使用new则指向bind当前传入的第一个参数。


此外,还牵扯到原函数可以添加自身方法属性。如果想要能够使用fun自身的原型方法还需要使用fBind.prototype = this.prototype,实现原型共用。但是对于引用类型属性值共享,不能在不改变其他实例情况下改变(一个原型方法或属性改变,所有引用的都会发生改变)。


Function.prototype.mybind = function (context) {
  let that = this
  let args = Array.prototype.slice.call(arguments, 1)
  function fBind() { // 执行bind函数
    let bindargs = Array.prototype.slice.call(arguments)
    return that.apply(this instanceof fBind ? this : context, args.concat(bindargs))
  }
  function Fn(){} // 两个类的原型并未公用,而是通过原型链的方式找到该原型方法
  Fn.prototype = this.prototype
  fBind.prototype = new Fn()
  return fBind
}


对于上述情况,可以使用一个函数中间件的形式,利用原型链去找到原函数原型方法或属性。


call


call与apply的差别只是处理参数的不同,其他均类似。


Function.prototype.mybind = function (context, ...args1) {
  let that = this
  function fBind(...args2) {
    return that.call(this instanceof fBind ? this : context, ...args1, ...args2)
  }
  function Fn() { }
  Fn.prototype = this.prototype
  fBind.prototype = new Fn()
  return fBind
}
目录
相关文章
|
监控 流计算
【极数系列】Flink集成DataSource读取文件数据(08)
【极数系列】Flink集成DataSource读取文件数据(08)
240 2
|
23天前
|
人工智能 搜索推荐 机器人
智能体是什么?3 分钟读懂 AI 智能体核心能力与应用场景
AI 智能体是具备自主理解、决策、执行任务能力的新一代 AI 系统,区别于传统 “指令响应式” 工具,它能像人类搭档一样拆解复杂需求、联动多能力模块完成闭环工作。NuwaAI 作为智能体数字人领域的标杆产品,已实现 “一句话生成智能体数字人”,其独创的双脑架构可支撑教育培训、电商直播、文旅表演、企业服务等 8 大场景,帮助用户将表达力转化为生产力,实测能降低 80% 的重复工作人力成本(数据来源:2025 年 AI 智能体行业白皮书)。
|
Docker 容器
windows10 docker D盘部署最终解决方案
docker D盘部署最终解决方案
3456 0
windows10 docker D盘部署最终解决方案
|
前端开发 Java 数据库连接
领域驱动设计(DDD):分层架构
在应用系统开发中,采用严格的、单一的、真正的的分层架构是可以的,但实际上我们已经采用了多种架构模式设计系统。当多种不同范式的架构混合在一起,你会不会出现“指鹿为马”的现象呢?
领域驱动设计(DDD):分层架构
|
信息无障碍
BUUCTF [WUSTCTF2020]find_me 1
BUUCTF [WUSTCTF2020]find_me 1
503 0
|
JavaScript
Vue2首页banner轮播
这篇文章介绍了如何在Vue 2框架中创建一个首页轮播图(Banner)组件,允许自定义轮播数据、切换间隔、宽度和高度,并提供了组件的实现代码和使用示例。
245 0
Vue2首页banner轮播
vscode——Prettier插件保存自动格式化
vscode——Prettier插件保存自动格式化
844 0
|
SQL 数据采集 存储
Dataphin V3.6版本发布啦!多项能力升级,助力企业提升全链路数据治理能力!
Dataphin V3.6版本全新上线概念建模、基线运维、全域数据质量监控、数据标准标准落标映射等核心功能,为企业建设贯穿事前、事中、事后的全链路数据治理能力添砖加瓦,助力提升资产价值。
38189 1
Dataphin V3.6版本发布啦!多项能力升级,助力企业提升全链路数据治理能力!
|
安全
常见智力题汇总(建议收藏)(上)
常见智力题汇总(建议收藏)(上)
547 0
|
存储 算法 内存技术
单片机原理与应用以及C51编程技术——硬件体系结构梳理(上)
单片机原理与应用以及C51编程技术——硬件体系结构梳理(上)
1387 0
单片机原理与应用以及C51编程技术——硬件体系结构梳理(上)