JavaScript中call、apply、bind实现原理详解

目录
  • 前言
  • 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
}

总结

到此这篇关于JavaScript中call、apply、bind实现原理的文章就介绍到这了,更多相关call、apply、bind原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Javascript中call,apply,bind方法的详解与总结

    以下内容会分为如下小节: 1.call/apply/bind方法的来源 2.Function.prototype.call() 3.Function.prototype.apply() 3.1:找出数组中的最大数 3.2:将数组的空元素变为undefined 3.3:转换类似数组的对象 4.Function.prototype.bind() 5.绑定回调函数的对象 6.call,apply,bind方法的联系和区别 1.call/apply/bind方法的来源 首先,在使用call,apply,

  • JavaScript函数之call、apply以及bind方法案例详解

    总结 1.相同点 都能够改变目标函数执行时内部 this 的指向 方法的第一个参数用于指定函数执行时内部的 this 值 支持向目标函数传递任意个参数 若不向方法的第一个参数传值或者传递 undefined.null,则在 JavaScript 正常模式下,目标函数内部的 this 指向 window 对象,严格模式下,分别指向 undefined.null. 2.区别 apply() 方法可接收两个参数,而 call() 和 bind() 方法则可接收多个参数. apply() 方法向目标函数

  • 全面解析JavaScript中apply和call以及bind(推荐)

    函数调用方法 在谈论JavaScript中apply.call和bind这三兄弟之前,我想先说下,函数的调用方式有哪些: •作为函数 •作为方法 •作为构造函数 •通过它们的call()和apply()方法间接调用 前面的三种调用方法,我们都知道且不在这篇文章的讨论范围内,就不说了. 下面我们来说说这第四种调用方法 通过call()和apply()间接调用 其实,我们可以将这两个函数看做是某个对象的方法,通过调用方法的方式来间接调用函数: function f(){} f.call(o); f.

  • 浅谈JavaScript中的apply/call/bind和this的使用

    fun.apply(context,[argsArray]) 立即调用fun,同时将fun函数原来的this指向传入的新context对象,实现同一个方法在不同对象上重复使用. context:传入的对象,替代fun函数原来的this: argsArray:一个数组或者类数组对象,其中的数组参数会被展开作为单独的实参传给 fun 函数,需要注意参数的顺序. fun.call(context,[arg1],[arg2],[-]) 同apply,只是参数列表不同,call的参数需要分开一个一个传入.

  • 实例讲解JavaScript中call、apply、bind方法的异同

    以实例切入,讲解JavaScript中call,apply,bind方法,供大家参考,具体内容如下 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> function MAN(name, sex, age) { this.name =

  • javascript中call,apply,bind的区别详解

    在JS中,这三者都是用来改变函数的this对象的指向的,他们有什么样的区别呢. 在说区别之前还是先总结一下三者的相似之处: 1.都是用来改变函数的this对象的指向的. 2.第一个参数都是this要指向的对象. 3.都可以利用后续参数传参. 那么他们的区别在哪里的,先看一个例子. var xw = { name : "小王", gender : "男", age : 24, say : function() { alert(this.name + " ,

  • JavaScript中的apply和call函数详解

    第一次翻译技术文章,见笑了! 翻译原文: Function.apply and Function.call in JavaScript 第一段略. 每个JavaScript函数都会有很多附属的(attached)方法,包括toString().call()以及apply().听起来,你是否会感到奇怪,一个函数可能会有属于它自己的方法,但是记住,JavaScript中的每个函数都是一个对象.看一下 这篇文章 ,复习一下(refresher)JavaScript特性.你可能还想知道JavaScrip

  • javascript中call,apply,bind函数用法示例

    本文实例讲述了javascript中call,apply,bind函数用法.分享给大家供大家参考,具体如下: 一.call函数 a.call(b); 简单的理解:把a对象的方法应用到b对象上(a里如果有this,会指向b) call()的用法:用在函数上面 var Dog=function(){ this.name="汪星人"; this.shout=function(){ alert(this.name); } }; var Cat=function(){ this.name=&qu

  • JavaScript原型继承和原型链原理详解

    这篇文章主要介绍了JavaScript原型继承和原型链原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在讨论原型继承之前,先回顾一下关于创建自定义类型的方式,这里推荐将构造函数和原型模式组合使用,通过构造函数来定义实例自己的属性,再通过原型来定义公共的方法和属性. 这样一来,每个实例都有自己的实例属性副本,又能共享同一个方法,这样的好处就是可以极大的节省内存空间.同时还可以向构造函数传递参数,十分的方便. 这里还要再讲一下两种特色的构造

  • javascript中href和replace的比较(详解)

    在使用javascript的时候,有时候对于经常使用的方法太熟悉而忽略了他们之间原理的细微差别. 举例如下: window.location.href,window.location.replace. 这两种方式都可以让页面跳转到一个新的页面,但是其中我就忽略了跳转之后的细节,比如返回的原来的页面. window.location.href中的href其实就是<a>标签中的href,使用这个进行页面跳转后,可以使用浏览器的后退按钮退回到原来的页面,也可以使用history.go(-1)函数跳转

  • JavaScript 中有关数组对象的方法(详解)

    JS 处理数组多种方法 js 中的数据类型分为两大类:原始类型和对象类型. 原始类型包括:数值.字符串.布尔值.null.undefined 对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象----函数(js中的一等对象).数组(键值的有序集合). 数组元素的添加 arrayObj.push([item1 [item2 [. . . [itemN ]]]]); 将一个或多个新元素添加到数组结尾,并返回数组新长度 arrayObj.unshift([item1 [item2 [. . .

  • Javascript中的迭代、归并方法详解

    迭代方法 在Javascript中迭代方法个人觉得尤为重要,在很多时候都会有实际上的需求,javascript提供了5个迭代方法来供我们操作,它们分别为: every() 对数组中的每一个项运用给定的函数,如果每项都返回true,那么就会返回true filter() 对数组中的每一个项运用给定的函数,把返回true的项组成一个新数组并返回 forEach() 对数组中的每一项运用给定的函数,但是没有任何的返回值 map() 对数组中的每一个项运用给定的函数并返回每次函数调用的结果组成新的数组

  • JavaScript中SetInterval与setTimeout的用法详解

    setTimeout 描述 setTimeout(code,millisec) setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式. 注:调用过程中,可以使用clearTimeout(id_of_settimeout)终止 参数 描述 code 必需,要调用的函数后要执行的 JavaScript 代码串. millisec 必需,在执行代码前需等待的毫秒数. setTimeinterval setInterval(code,millisec[,"lang"]) 参数

  • JavaScript中闭包的写法和作用详解

    1.什么是闭包 闭包是有权访问另一个函数作用域的变量的函数. 简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内.而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量.参数和声明的其他内部函数.当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包. 2.变量的作用域 要理解闭包,首先要理解变量的作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变

  • JavaScript中Number对象的toFixed() 方法详解

    定义和用法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字. 语法 NumberObject.toFixed(num) 参数 描述 num 必需.规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围.如果省略了该参数,将用 0 代替. 返回值 返回 NumberObject 的字符串表示,不采用指数计数法,小数点后有固定的 num 位数字.如果必要,该数字会被舍入,也可以用 0 补足,以便它达到指定的长度.如果 num 大于 l

随机推荐