写在前面

call、apply、bind其实用法都差不多 都是改变 this 指向,只是后面参数的传递有些许不同

  • call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,‘成都’, …,‘string’ )
  • apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,[‘成都’, …, ‘string’ ])
  • bind 除了返回是函数以外,它 的参数和 call 一样

1. call 源码解析 es3 es6

function add(c, d) {
  return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };

// ES3 call 实现
Function.prototype.es3call = function (context) {
  var content = context || window;
  content.fn = this;
  var args = [];
  // arguments是传入的参数(实参)的类数组对象 函数自带
  for (var i = 1, len = arguments.length ; i < len; i++) {
    args.push('arguments[' + i + ']');
  }
  var result = eval('content.fn('+args+')');
  delete content.fn;
  return result;
}
console.error(add.es3call(obj, 3, 4)); // 10

// ES6 call 实现
Function.prototype.es6call = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i]);
  }
  const result = context.fn(...args);
  delete context.fn;
  return result;
}

console.log(add.es6call(obj, 3, 4)); // 10

apply、bind的 es6 写法大家可以自己尝试写一下

2. apply 源码解析 es3 es6

function add(c, d) {
  return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };

// ES3 apply 实现
Function.prototype.es3apply = function (context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  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 + ')')
  }
  delete context.fn;
  return result
}

console.log(add.apply(obj, [1, 2])); // 6
// ES6 apply 实现
Function.prototype.es6apply = function (context, arr) {
    var context = context || window;
    context.fn = this;
    var result;
    if (!arr) {
      result = context.fn();
    } else {
      if (!(arr instanceof Array)) throw new Error('params must be array');
      result = context.fn(...arr);
    }
    delete context.fn;
    return result;
  }
  
  console.error(add.es6apply(obj, [1, 2])); // 6

3. bind 源码解析 es3 es6

function add(c, d) {
  return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };

// ES3 bind 实现
Function.prototype.es3bind= function (target) {
  target = target || window;//如果没有传入,就为window
  var self = this;//谁调用myBind,this就指向谁
  var args = [].slice.call(arguments, 1);//args:[arguments[1],arguments[2]....]
  var temp = function () { };
  var fn = function () {
      var fnArgs = [].slice.call(arguments, 0);
      //this 如果new fn()  this 指向构造出来的对象,否则为window ;this instanceof fn看this的原型链上有没有fn的原形
      return self.apply(this instanceof fn ? this : target, args.concat(fnArgs));
  }
  temp.prototype = this.prototype;
  fn.prototype = new temp();  //形成继续关系  fn.prototype.__proto__ == this.prototype  true
  return fn;
}

console.log(add.es3bind(obj, 3, 4)()); // 10

// ES6 bind 实现
Function.prototype.es6Bind = function(context, ...rest) {
  var self = this;//谁调用myBind,this就指向谁
  return function fn(...args) {
    return this instanceof fn ? new self(...rest, ...args) : self.apply(context, rest.concat(args))
  }
}

console.log(add.es6Bind(obj, 3, 4)()); // 10