JavaScript实现callapply和bind

Posted A_山水子农

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript实现callapply和bind相关的知识,希望对你有一定的参考价值。

  每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。详细内容查看JavaScript中call()、apply()和 bind()方法

1、call方法的实现

下面看一个使用call方法的实例:

function add(c, d)  
  return this.a + this.b + c + d  
  
var o = a:1, b:3 
add.call(o, 5, 7) // 16

  add.call(o, 5, 7)使add函数中的this指向o对象,并且执行了add函数。依据上例把o对象改造成如下:

var o= 
    a: 1,
    b: 3,
    add: function(c, d) 
        return this.a + this.b + c + d
    
;

o.add(5, 7); // 16

  给o对象添加一个add属性,这个时候 this 就指向了 o,o.add(5,7)得到的结果和add.call(o, 5, 6)相同。但是给对象o添加了一个额外的add属性,这个属性我们是不需要的,所以可以使用delete删除它。

所以我们使用js实现call方法的步骤可以分为:

将函数设为对象的属性
执行该函数
删除该函数

以上个例子为例,就是:

// 第一步
o.fn = bar
// 第二步
o.fn()
// 第三步
delete o.fn

使用ES3实现call方法

/**
 * @description 使用ES3实现call方法
 * @param Object context call方法一个指定的this值
 * @returns Object, String, Number, Boolean 返回调用函数的值
 */
Function.prototype.call = function (context) 
    // context为null的时候,context为window
    var context = context || window
    // 获取调用call的函数
    context.fn = this
    // 获取call方法的不定长参数
    var args = []
    for(var i = 1, len = arguments.length; i < len; i++) 
        args.push('arguments[' + i + ']')
    
    // 运行fn函数并返回结果,
    // eval(string)通过计算string得到的值
    var result = eval('context.fn(' + args +')')
    // 删除fn属性
    delete context.fn
    // 返回结果
    return result;

/**
 * @description 测试call方法
 * @param Number c,d 函数的参宿
 * @returns Number 返回add函数的计算结果
 */
function add(c, d)  
  return this.a + this.b + c + d  
  
var o = a:1, b:3 
add.call(o, 5, 7) // 16

使用ES6实现call方法

/**
 * @description 使用ES6数组的扩展运算符(...)实现call方法
 * @param Object context call方法一个指定的this值
 * @returns Object, String, Number, Boolean 返回调用函数的值
 */
Function.prototype.call = function(context) 
  // context为null的时候,context为window
  var context = context || window
  // 获取调用call的函数
  context.fn = this
  // 获取call方法的不定长参数
  var args = []
  for (var i = 1, len = arguments.length; i < len; i++) 
   args.push(arguments[i])
  
  // 使用ES6扩展运算符(...)执行函数,返回结果
  var result = context.fn(...args)
  // 删除fn属性
  delete context.fn
  // 返回结果
  return result;

更简单的ES6写法:

/**
 * @description 使用ES6函数的rest参数和数组的扩展运算符实现call方法
 * @param Object context call方法一个指定的this值
 * @param Object, String, Number, Boolean context call方法一个指定的this值
 * @returns Object, String, Number, Boolean 返回调用函数的值
 */
Function.prototype.call = function(context, ...args) 
  // 使用ES6函数的rest参数(形式为...变量名),args是数组
  // context为null的时候,context为window
  var context = context || window
  // 获取调用call的函数
  context.fn = this
  // 使用ES6扩展运算符(...)执行函数,返回结果
  var result = context.fn(...args)
  // 删除fn属性
  delete context.fn
  // 返回结果
  return result;

2、apply方法的实现

  apply和call方法实现类似,apply和call方法的区别仅在于接收参数的方式不同。对于apply()方法而言,第一个参数是作用域没有变化,变化的只是其余的参数apply使用数组或者arguments对象,call直接传递给函数。

使用ES3实现apply方法

/**
 * @description 使用ES3实现apply方法
 * @param Object context apply方法一个指定的this值
 * @param Array arr apply方法传递给调用函数的参数
 * @returns Object, String, Number, Boolean 返回调用函数的值
 */
Function.prototype.apply= function (context, arr) 
    // context为null的时候,context为window
    var context = context || window
    // 获取调用apply的函数
    context.fn = this
    var result
    // 判断apply是否只有一个参数
    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 + ')')
    
    // 删除fn属性
    delete context.fn
    // 返回结果
    return result;

/**
 * @description 测试apply方法
 * @param Number c,d 函数的参宿
 * @returns Number 返回add函数的计算结果
 */
function add(c, d)  
  return this.a + this.b + c + d  
  
var o = a:1, b:3 
add.apply(o, [5, 7]) // 16

使用ES6实现apply方法

/**
 * @description 使用ES6数组的扩展运算符实现apply方法
 * @param Object context apply方法一个指定的this值
 * @param Array arr apply方法传递给调用函数的参数
 * @returns Object, String, Number, Boolean 返回调用函数的值
 */
Function.prototype.call = function(context, arr) 
  // context为null的时候,context为window
  var context = context || window
  // 获取调用apply的函数
  context.fn = this
  // 使用ES6扩展运算符(...)执行函数,返回结果
  var result = context.fn(...arr)
  // 删除fn属性
  delete context.fn
  // 返回结果
  return result

3、bind方法的实现

  bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。先看一个使用bind方法的实例:

function foo(c, d)   
  this.b = 100
  console.log(c)
  console.log(d) 
  console.log(this.a)
  
var func = foo.bind(a: 1, 'cc') 
func('dd') //cc dd 1
new func('dd') //cc dd undefined

  当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。所以上例中使用func为构造函数时,this不会指向a: 1对象,this.a的值为undefined。

/**
 * @description 实现bind方法
 * @param Object context bind方法一个指定的this值
 * @returns Function 返回一个函数
 */
Function.prototype.bind = function (context) 
  // 判断绑定bind方法的是不是函数
  if (typeof this !== "function") 
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  
  // 绑定函数赋值给self
  var self = this;
  // 获取bind2函数从第二个参数到最后一个参数
  var args = Array.prototype.slice.call(arguments, 1);
  // 调用bind方法返回的函数
  var fBound = function () 
    // 获取函数的参数
    var bindArgs = Array.prototype.slice.call(arguments);
    // 返回函数的执行结果
    // 判断函数是作为构造函数还是普通函数
    // 构造函数this instanceof fNOP返回true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值。
    // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
    return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs))
  
  // 创建空函数
  var fNOP = function () ;
  // fNOP函数的prototype为绑定函数的prototype 
  fNOP.prototype = this.prototype;
  // 返回函数的prototype等于fNOP函数的实例实现继承
  fBound.prototype = new fNOP();
  // 以上三句相当于Object.create(this.prototype)
  // 返回函数
  return fBound;

使用ES6实现bind方法

if (typeof Function.prototype.bind !== 'function') 
    Function.prototype.bind = function(context, ...rest) 
        if (typeof this !== 'function') 
            throw new TypeError('invalid invoked!')
        
        var self = this
        return function F(...args) 
            if (this instanceof F) 
                return new self(...rest, ...args)
            
            return self.apply(context, rest.concat(args))
        
    

参考链接:

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

不能使用call,apply,bind,如何用js实现call或者apply的功能?

JavaScript深入之bind的模拟实现

以上是关于JavaScript实现callapply和bind的主要内容,如果未能解决你的问题,请参考以下文章

原生JavaScript实现callapply和bind

源码来袭:callapply手写实现与应用

JavaScript中this这号人物和callapply的详解

JavaScript 函数 CallApply

Javascript中callapply函数浅析

JavaScript之手撕callapply