JavaScript实现call、apply和bind

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27626333/article/details/78794376

  每个函数都包含两个非继承而来的方法: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直接传递给函数。

扫描二维码关注公众号,回复: 5291359 查看本文章

使用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的模拟实现

猜你喜欢

转载自blog.csdn.net/qq_27626333/article/details/78794376
今日推荐