call,apply,bind的用法与区别

140 阅读4分钟

call() 用法

call、apply的根本作用是为了实现继承或复用方法。

继承是面向对象软件技术当中的一个概念,与多态、封装共为面向对象的三个基本特征。继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。

JavaScript是面向对象的,是基于原型链的继承。

实现继承

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

Product.prototype = {
  printPrice: function() { 
    console.log(`The ${this.name}'s price is ${this.price} yuan`)
  }
}

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

Food.prototype = Product.prototype

const cheese = new Food('cheese', 5)

console.log(cheese.name);
// Expected output: "cheese"
console.log(cheese.price);
// 5
cheese.printPrice()
// The cheese's price is 5 yuan

Product.call(this, name, price) 相当于在Food方法中实现this.name = namethis.price = price。这里的 call() 调用了 Product 的构造方法,并改变了 this 指向(this 原指向 Product,这里指向 Food)。

使用class语法糖实现上述继承

class Product {
  constructor(name, price) {
    this.name = name
    this.price = price
  }

  printPrice() {
    console.log(`The ${this.name}'s price is ${this.price} yuan`)
  }
}

class Food extends Product{
  constructor(name, price) {
    super(name, price)
    this.category = 'food'
  }
}

const cheese = new Food('cheese', 5)

console.log(cheese.name);
// Expected output: "cheese"
console.log(cheese.price);
// 5
cheese.printPrice()
// The cheese's price is 5 yuan

使用 class 语法糖可以更方便的实现私有属性和私有方法

如何实现私有属性和私有方法? 自执行函数 + 闭包

实现复用

说是复用,其实不太准确,但可以通过复用的思路来理解它。

function greet() {
  var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
  console.log(reply);
}

var obj = {
  animal: 'cats', sleepDuration: '12 and 16 hours'
};

greet.call(obj);  // cats typically sleep between 12 and 16 hours

greet.call(obj) 相当于 obj.greet(),但 obj 对象并没有 greet 方法。call 的作用相当于是在 obj 对象上复用了 greet 方法。如下:

var obj = {
  animal: "cats",
  sleepDuration: "12 and 16 hours",
  greet: function () {
    var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
    console.log(reply);
  },
};

obj.greet() // cats typically sleep between 12 and 16 hours

如有更好的理解,请评论区告诉我。

匿名函数调用 call

var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}

使用匿名函数调用call,更方便我们理解。这里的匿名函数中的 this ,指向 animals[i]

无需使用 this,调用 call

console.log(Math.max.call(undefined, 1, 100, 200))

相当于是

console.log(Math.max(1, 100, 200))

什么情况下,可以使用call

除了上面列举的用法外,还有比如:类数组对象并不能显式的调用数组方法,但是可以通过 call 调用;上述的Number 集合可以通过 call 调用 Math 方法等。

类数组对象:

const arrayLike = {
    length: 5,
    0: 2,
    3: 4,
    4: 1
}

直接使用 Array.prototype.map

arrayLike.map(a => console.log(a))

TypeError: arrayLike.map is not a function

可以用过 call 方法调用

console.log(Array.prototype.map.call(arrayLike, a => a * 2))
// [ 4, <2 empty items>, 8, 2 ]
// <2 empty items> 是因为 arrayLike 没有下标 1, 2

call,apply区别

call() 方法的语法和作用与 apply()方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

bind

定义

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

语法

function.bind(thisArg[, arg1[, arg2[, ...]]])

参数

  • thisArg

    调用绑定函数时作为 this 参数传递给目标函数的值。

  • arg1, arg2, ...

    当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数

描述

因为bind()绑定this之后,并没有立即执行,而是返回一个原函数的拷贝。相当于是在call方法上定义了一个钩子,因为在调用绑定函数(返回值-原函数的拷贝)时,内部就是调用了call(thisArg, arguments)。其中arguments 包括预置的参数列表和调用时的参数列表。

示例

不传参数

因为 module.getX() 没有参数, 就算传了参数也没用。

this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

预置参数

1, 2, 3为预置参数,这里不需要绑定 this 值( =null ),是因为 addArguments 函数实现没有用到 this

function addArguments() {
  return Array.from(arguments).reduce((acc, current) => acc + current)
}

const add1 = addArguments.bind(null, 1, 2, 3)
const result = add1(4, 5, 6)
console.log(result) // 21

add1(4, 5, 6) 的内部执行过程:

addArguments.call(null, 1, 2, 3, 4, 5, 6) // 21

addArguments 为了便于理解这里使用的原函数,其实是原函数的拷贝(bind() 的返回值)

配合 setTimeout

在默认情况下,使用 window.setTimeout()时,this 关键字会指向 window(或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
  // 这里需要改变this指向 -> LateBloomer
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后,调用 'declare' 方法

bind 与 call 、 apply的区别

  • bind 与 call 、 apply 一样也会改变调用函数的this指向。
  • bind并不会立即执行调用函数,而是会返回一个调用函数的拷贝,并拥有指定的 this 值和初始参数。call、apply会立即执行调用函数。