JavaScript的apply、call、bind方法

发布时间 2023-12-29 23:26:20作者: 苏沐问

JavaScript的apply、call、bind方法

概述

简述这三个方法存在一定的迷惑性 ,而且对于刚看ES6的人来说,十分难理解,这里为了以后我可能会复习到这个知识点,做出详解。总的来说,这三个方法都是将某某某(某01)绑定在某某某(某02)上,然后执行这个被绑定的某某某(某01),或者单纯就是绑定不执行。


详解

从前端必备网站 MDN 上我们可以得知以上三种方法的完全体。


Function.prototype.apply(thisArg , argsArray); //apply方法

Function.prototype.call(thisArg , ...args); //call方法

Function.prototype.bind(thisArg , ...args); //bind方法


然后我们来分别看看这三个方法的例子,首先是call,我把它放在第一个讲,因为我觉得它很有代表性。


Call方法

function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  //这里调用了call方法
  Product.call(this, name, price);
  this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// Expected output: "cheese"

第一眼看上去,这个方法到底是啥?怎么这么难理解?它到底做了什么?


解析

我们不考虑MDN对这个方法参数的专业解释,来用我们自己的语言描述这个方法。


下面给出我们在上面代码块中调用call方法的部分。

Product.call(this, name, price);

这句代码到底做了什么事情,首先我们从它的this入手,有这样一句话


构造函数中this指向对象实例


所以this指向的,就是function Food(...)构造方法的实例,call方法的第一个参数是谁我们就找到了。

接下来是call方法的第二个参数,注意call方法的完全体的第二个参数名字为 “argsArray” ,这个名字的意思是什么?参数数组,对了,所以 “name” 和 “price” 应该有一个统一的名称,参数数组中的参数。

现在我们解释了代码块中调用call方法部分的两个参数,那么,接下来的问题是call方法利用这两个参数做了什么事情呢?


我们再把call方法的完全体拿过来看一看。


Function.prototype.apply(thisArg , argsArray);


除去原型链(prototype)部分,这个方法写为 Function.apply(thisArg , argsArray);

Function,Function是谁?在代码块中调用call方法部分中,Function是Product,call方法会把参数传给Product。

注意,call方法一旦调用就立即执行对应的Function。


经过上面的讨论我们基本上分析完了,下面给出我们的通俗理解。


“我们将Product构造方法,绑定给了Food构造方法,并将Product构造方法的this指向Food构造方法的实例。也就是说我们执行Food构造方法时调用Product构造方法,这个调用是Product构造方法的内容在Food构造方法的内部调用


那这样说我直接在Food构造方法中调用Product构造方法就好了,为什么要用到call方法?

(这么问的话说明你还没有理解我上面的这段话,我给个例子)

我们把这句话翻译成代码。

function Food(name, price) {
  Product(name, price);
  this.category = 'food';
}

把我们的通俗理解翻译成代码如下

function Food(name, price) {
  //Product.call(this, name, price);和下面代码等价
  this.name = name;
  this.price = price;
  ...
}

看出区别了吗?两个代码块调用this的指向是不同的,Product(name, price);方法的this指向Product构造方法的实例,而后者代码块的this指向Food构造方法的实例。这样就使得console.log(new Food('cheese', 5).name);运行时,呈现两种完全不同的结果。


后者代码块的new Food('cheese', 5)运行时会将参数直接传给Product(name, price);然后呢?然后就没有了!Product(name, price);将参数传给了Product构造方法,而Product构造方法的this指向他自己的实例,赋值当然也会赋给它自己的实例。


这里附上Product构造方法的代码。

function Product(name, price) {
  this.name = name;//赋值
  this.price = price;//赋值
}

所以当运行new Food('cheese', 5).name时,返回的值是undefined,因为new Food('cheese', 5)产生的实例中,根本不含有name和price属性!


因此,想让name和price的值赋给Food构造方法的实例,必须改变Product构造方法中this的指向,让Product构造方法的this指向Food构造方法的实例才行。


call方法就为我们做了这件事,这也是call方法第一个参数的意义所在。


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

经过call方法的磨练,想必理解上述代码十分简单,但是要注意一件事情,即bind方法调用后,它对应的Function不会立即执行,要等我们去调用才执行。


apply方法

const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);

console.log(max);
// Expected output: 7

const min = Math.min.apply(null, numbers);

console.log(min);
// Expected output: 2

这里也不给出具体的解析了,同理call方法即可。注意,apply方法调用后,它对应的Function会立即执行。