javasrcipt中apply,call与bind的区别?手写一个call,apply,bind?

880 阅读4分钟

apply与call的作用

apply与call的作用是在特定的作用域中调用函数

举个例子,这是函数没有参数的情况

num = 1;// 默认声明到全局作用域

function logNum() {
  console.log(this.num);
}

logNum(); // 1
logNum.apply({ num: 99 });// 99
logNum.call({ num: 88 });// 88

如上代码可知:call和apply接收的第一个参数都是一个作用域,在作用域里面num都被赋了不一样的值,所以最后this.num输出不一样的值。

当函数有参数时,是这样的:

sum = 1;

function getSum(num1, num2) {
  this.sum = this.sum + num1 + num2;
  console.log(this.sum);
}

getSum(2, 3); // 6
getSum.apply({ sum: 100 }, [99, 33]); // 232
getSum.apply({ sum: 100 }, [99, 33, 999]); // 232
// getSum.apply({sum:100},99,33); // TypeError: CreateListFromArrayLike called on non-object
getSum.call({ sum: 100 }, 99, 133); // 232
getSum.call({ sum: 100 }, 99, 133, 99); // 232
getSum.call({ sum: 100 }, [99, 33]); // 10099,33undefined 自动转为string再拼接了

由以上例子可以看出,apply与call的不同点只是接收的参数不一样

  • apply接收的参数是一个数组,这个数组的全部会按照顺序传递给执行的函数。
  • call接收的参数是和执行函数一样的形式,一个个赋值给执行函数,就是说除了第一个作用域参数,其他参数都会传递给执行函数

注意

有一个比较特殊的是apply函数还可以接收arguments对象

sum = 1;

function getSum(num1, num2) {
  this.sum = this.sum + num1 + num2;
  console.log(this.sum);
}

function myGetSum() {
  console.log(arguments); // [Arguments] { '0': 99, '1': 88 }
  getSum.apply({ sum: 100 }, arguments);
}
getSum(1, 2);
myGetSum(99, 88);

所以apply的第二个参数只能是数组或者arguments对象,假设是普通的对象,无法被解析,最终只会传入undefined到执行函数中。

bind的作用

不接收参数时

num = 1;

function logNum() {
  console.log(this.num);
}

const myLogNum = logNum.bind({ num: 100 });
logNum(); // 1
myLogNum(); // 100

接收参数时

sum = 1;

function getSum(num1, num2) {
  this.sum = this.sum + num1 + num2;
  console.log(this.sum);
}

const myGetSum = getSum.bind({ sum: 100 }, 100, 200);
getSum(100,200); // 301
myGetSum(); // 400

bind的作用是将一个作用域绑定到一个函数中,再返回这个已经绑定作用域的函数。 执行该函数时与apply和call是一样的效果。对!需要执行,不会直接调用。

apply,call与bind的相同点

  • 都是函数非继承而来的方法
    • 函数有两个属性:
      • length
      • prototype(来继承属性与方法)
    • 函数有三个非继承而来的方法:
      • call
      • apply
      • bind
  • 都可以改变函数对运行作用域,也可以说改变this对指向

apply,call与bind的不同点

  • 返回值不一样

    bind返回值类型固定:是一个绑定作用域后的函数实例

    apply和call的返回值类型不固定:是执行函数的返回值(啥都行,如果没有返回值则返回:undeifined)

  • 执行情况不一样

    bind返回一个函数之后需要调用才执行

    apply和call直接执行函数

apply与call的不同点

接收的参数不一样:

  • apply:接收一个包含多个参数的数组(oThis,[args1,...])
  • call:接收多个参数的列表(oThis,args1,...)

手写bind(简单版,了解原理)

bind其实是通过call或者apply实现的,按照接收参数的形式call更适合一些,当然apply也可以使用argument对象或者数组来实现,但使用call可以节省参数解析的时间。

实现一个简单的bind函数

Function.prototype.myBind = function (runArea,...args) {
  const oThis = this; // 函数实例
  return function () {
    return oThis.call(runArea,...args); // 加上return 是因为函数可能会有返回值
  };
};

num = 1;

function logNum() {
  console.log(this.num);
}

logNum(); // 1
const logByBind = logNum.bind({ num: 100 });
logByBind(); // 100
const logByMyBind = logNum.myBind({ num: 100 });
logByMyBind(); // 100

步骤其实挺简单的:

  • 声明一个bind函数(myBind)
  • 在bind函数里面返回一个函数
  • 在返回的函数中,执行call方法,加上return可以返回方法的返回值

手写call(简单版,了解原理)

Function.prototype.myCall = function (runArea, ...args) {
  const oThis = runArea || window;
  const fn = Symbol('fn'); // 防止替换掉函数实例本身的fn方法
  oThis[fn] = this; // 绑定函数实例的作用域
  oThis[fn](...args); //执行,看有人用eval来执行,不太推荐
  delete oThis[fn]; //执行后删除这个方法
};

手写apply(简单版,了解原理)

Function.prototype.myApply = function (runArea, args) {
  const oThis = runArea || window;
  const fn = Symbol("fn"); // 防止替换掉函数实例本身的fn方法
  oThis[fn] = this; // 绑定函数实例的作用域
  oThis[fn](...args); //执行,看有人用eval来执行,不太推荐
  delete oThis[fn]; //执行后删除这个方法
};

简单的实现了下,发现apply和call是可以一样的,因为参数这里用了解构处理,不管是数组还是对象都可以处理成列表形式的参数。

总结一下

一遍写下来发现这三个改变作用域的函数真没那么复杂,关键是多练习多比较,今天就先到这里,未能再深究,要开始写需求了,今天还用到了Symbol,一直以为它没啥用处呢。

有错误之处希望大家帮忙指出!