jiaochunxiao / fe-blog

好好学习,天天向上
1 stars 1 forks source link

call, apply的区别,及call,apply,bind的模拟实现 #5

Open jiaochunxiao opened 4 years ago

jiaochunxiao commented 4 years ago

主要区别

call,apply的功能相同,区别在于传参的方式不同,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。:

call核心

Function.prototype.call = function (context) {
  // 如果第一个参数传入的是null或者是undefined, this指向global或者window
  // 若第一个参数不是null、undefined, 那么必须是一个对象
  if (!context) {
    context = typeof window === 'undefined' ? global : window;
  }
  context.fn = this; // this指向当前的函数(Function)实例
  let res = [...arguments].slice(1); // 获取除去this指向的对象以外
  let result = context.fn(...res); // 隐式绑定,当前函数的this 指向 context;
  delete context.fn;
  return result;
}

来看一道题,思考一下:

function fn1() {
  console.log(1);
}
function fn2() {
  console.log(2);
}

fn1.call(fn2); // 输出1
fn1.call.call(fn2); //输出2

fn1.call(fn2);

按照上面的理解

1、call 方法中的this是fn1 2、把call方法中的this(fn1)中的this指向fn2 3、调用 call方法中的this

所以调用的是 fn1 ,此时fn1中的 this 指向的是 fn2。 但是这个方法里面并没有使用this,而是直接输出了1。

fn1.call.call(fn2);

按照上面的理解

1、 call 方法中的 this 是 fn1.call【所有函数都可以调用call,调用的是原型上call方法】 2、把call方法中的this (fn1.call) 中的this 指向fn2 3、调用call方法中的this

所以调用的是 fn2(这里有些绕,多念叨念叨琢磨琢磨),此时fn1.call中的this指向的是fn2。 它改变了call方法(Function.prototype原型上的call)的this指向。 此处调用了call方法中的this,即调用了fn2,输出了2。

apply模拟实现

apply与call实现类似,区别在于两者的参数不同,apply的第二个参数为数组或者类数组。


Function.prototype.apply = function (context, rest) {
  if (!context) {
    //context为null或者是undefined时,设置默认值
    context = typeof window === 'undefined' ? global : window;
  }
  context.fn = this;
  let result;
  if (rest === undefined || rest === null) {
    //undefined 或者 是 null 不是 Iterator 对象,不能被 ...
    result = context.fn(rest);
  } else if (typeof rest === 'object') {
    result = context.fn(...rest);
  }
  delete context.fn;
  return result;
}

bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

bind() 函数会创建一个新的绑定函数(bound function,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。

模拟实现:

Function.prototype.bind = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('not a function');
  }
  let self = this;
  let args = [...arguments].slice(1);
  function Fn() {};
  Fn.prototype = this.prototype;
  let bound = function() {
    let res = [...args, ...arguments]; //bind传递的参数和函数调用时传递的参数拼接
    context = this instanceof Fn ? this : context || this;
    return self.apply(context, res);
  }
  bound.prototype = new Fn();
  return bound;
}
var name = 'Jack';
function person(age, job, gender){
    console.log(this.name , age, job, gender);
}
var Yve = {name : 'Yvette'};
let result = person.bind(Yve, 22, 'enginner')('female');

另附MDN(原理一样):

Function.prototype.bind = function() {
  var slice = Array.prototype.slice;
  var thatFunc = this, thatArg = arguments[0];
  var args = slice.call(arguments, 1);
  if (typeof thatFunc !== 'function') {
    throw new TypeError('Function.prototype.bind what is trying to be bound is not callable');
  }
  return function() {
    var funcArgs = args.concat(slice.call(arguments));
    return thatFunc.apply(thatArg, funcArgs);
  }
}

参考: