14

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

 3 years ago
source link: https://blog.csdn.net/dreamer_sen/article/details/112172575
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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(来继承属性与方法)
    • 函数有三个非继承而来的方法:
      • apply
  • 都可以改变函数对运行作用域,也可以说改变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,一直以为它没啥用处呢。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK