1

call和apply的作用

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数,该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组
在说实现自定义call、apply之前,我们首先看下一段代码

var age = 6;
var person =  {
    age: 3,
    fn: function() {
        console.log(this.age);
    }
}
var fn = person.fn;
fn();
person.fn();

结果是

6
3

为什么呢?记住一句话:this的最终指向的是那个调用它的对象
var fn = person.fn,将personfn方法赋值给fn,但未调用,而fn()调用的时候实际等于window.fn(),所以fn中的this指向window,所以fn()的结果是window.age = 6
person.fn()person这个对象调用的,所以此时fn函数中this的指向是person这个对象。所以this.age = 3

我们自己实现call方法,思路就是基于此

Function.prototype.myCall = function(_context) {
  // 当_context未传值或者为null或者undefined,context指向window
  var context = _context || window;
  // 给对象添加一个方法,这个this就是使用call使用的函数
  context.fn = this;
  // 拼接执行eval时候的参数
  var args = [];
  for(var i = 1; i < arguments.length; i++) {
    args.push('arguments['+ i +']');
  }
  // 字符串拼接数组,数组隐式转换成args.toString()即'context.fn('+ args +')', => 'context.fn(arguments[1])'
  var ret = eval('context.fn('+ args +')');
  delete context.fn;
  return ret;
}
Function.prototype.myCallEs6 = function(context = window) {
  // 给对象添加一个方法,这个this就是使用call使用的函数
  context.fn = this;
  // 获取除了第一个以外参数
  const args = [...arguments].slice(1);
  // 执行方法,此时执行方法的this指向的是context
  const ret = context.fn(...args);
  delete context.fn;
  // 返回执行结果
  return ret;
}
const person = {
  age: 3
};
function fn(name) {
  this.name = name;
  console.log(this);
}
fn.myCall(person, 'xzf');
fn.call(person, 'xzf');

call和apply作用相同,仅仅只是参数不同,apply的参数以数组的形式传入,所以我们只要做一下修改

Function.prototype.myApply = function(_context) {
  // 当_context未传值或者为null或者undefined,context指向window
  var context = _context || window;
  // 给对象添加一个方法,这个this就是使用call使用的函数
  context.fn = this;
  var ret;
  var args = arguments[1];
  if(args) {
    // 假设args为['123','abc'],['123','abc'].toString()会转换成'123,abc'
    // 执行eval的时候,会变成一个数字,一个变量,此时是不存在变量abc的
    // 需要通过上面mycall类似的方法进行转换
    var argsArr = [];
    for(var i = 0; i < arguments.length; i++) {
      argsArr.push('args['+ i +']');
    }
    ret = eval('context.fn('+ argsArr +')');
  } else {
    ret = context.fn();
  }
  delete context.fn;
  return ret;
}
// es6
Function.prototype.myApplyEs6 = function(context = window) {
  // 当_context未传值或者为null或者undefined,context指向window
  // 给对象添加一个方法,这个this就是使用call使用的函数
  context.fn = this;
  let ret;
  // 如果有参数,展开数组参数传入
  if(arguments[1]) {
    ret = context.fn(...arguments[1]);
  } else {
    ret = context.fn();
  }
  delete context.fn;
  return ret;
}

crazyPupil
37 声望4 粉丝