比较一下apply/call/bind ?

简介: 本文首发于微信公众号“前端徐徐”,详细介绍了 JavaScript 中 `apply`、`call` 和 `bind` 方法的概念、使用场景及手动实现。主要内容包括:- **apply**:使用数组作为参数调用函数,并指定 `this`。- **call**:直接传递参数调用函数,并指定 `this`。- **bind**:返回一个绑定了 `this` 和部分参数的新函数。文章还对比了这三个方法的区别,并提供了手动实现的代码示例。

本文首发微信公众号:前端徐徐。

apply

概念

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

func.apply(thisArg)
func.apply(thisArg, argsArray)

thisArg

func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

argsArray (可选)

一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

返回值

调用有指定 this 值和参数的函数的结果。

使用

  1. 改变函数内this的指向
const person = {
  name: 'John',
  greet() {
    console.log(`Hello, my name is ${this.name}`)
  }
}
const greetFunc = person.greet
greetFunc() // Hello, my name is undefined
greetFunc.apply(person) // Hello, my name is John
  1. 将数组作为参数传递给函数
function sum(x, y, z) {
  return x + y + z
}
const numbers = [1, 2, 3]
sum.apply(null, numbers) // 6
  1. 结合函数的剩余参数使用
const numbers = [1, 2, 3, 4, 5]
Math.max(...numbers) // 5 
Math.max.apply(null, numbers) // 5
  1. 绑定回调函数的this指向
const handler = {
  message: 'Hello World',
  printMessage() {
    console.log(this.message) 
  }
}
document.body.addEventListener('click', handler.printMessage) // Unable to print message
document.body.addEventListener('click', handler.printMessage.bind(handler)) // Bind this
document.body.addEventListener('click', handler.printMessage.apply.bind(handler.printMessage, handler)) // Apply bind

手动实现

🏅关键点:apply 的两个参数是函数运行的上下文 this 和参数数组

Function.prototype.myApply = function(context, args) {
  // 判断
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  // 唯一key/防止context对象有key/不能随便定义
  const symbolKey = Symbol()
  // 处理边界情况
  context = context || window;
  context[symbolKey] = this;
  const result = args ? context[symbolKey](...args) : context[symbolKey]();
  // 执行完借用的函数后,删除掉
  delete context[symbolKey];
  return result;
}

call

概念

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

func.call(thisArg, arg1, arg2, ...)

thisArg

可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

arg1, arg2, ...

指定的参数列表。

返回值

使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

使用

在JavaScript中,call()方法和apply()方法很相似,也有一些常见的使用场景:

  1. 改变函数内this的指向
const person = {
  name: 'John',
  greet() {
    console.log(`Hello, my name is ${this.name}`)
  }
}
const greetFunc = person.greet
greetFunc() // Hello, my name is undefined
greetFunc.call(person) // Hello, my name is John
  1. 借用其他对象的方法
const person1 = {
  name: 'John',
  greet() {
    console.log(`Hello, my name is ${this.name}`)
  }
}
const person2 = {
  name: 'Sarah'
}
person1.greet.call(person2) // Hello, my name is Sarah

这里person2借用了person1的greet方法。

  1. 应用函数,指定参数
function greet(lang) {
  if (lang === 'en') {
    console.log('Hello World') 
  } else if (lang === 'fr') {
    console.log('Bonjour le monde')
  }
}
greet.call(null, 'fr') // Bonjour le monde

call()允许直接传递函数参数。

  1. 结合函数的剩余参数使用
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0)
}
const nums = [1, 2, 3, 4, 5]
sum.call(null, ...nums) // 15

手动实现

🏅关键点:call 接收多个参数,第一个为上下文 this,后面为函数参数

Function.prototype.myCall = function(context, ...args) {
  // 判断
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  // 唯一key/防止context对象有key/不能随便定义
  const symbolKey = Symbol()
  // 处理边界情况
  context = context || window;
  context[symbolKey] = this;
  const result = args ? context[symbolKey](...args) : context[symbolKey]();
  // 执行完借用的函数后
  delete context[symbolKey];
  return result;
}

bind

概念

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

thisArg

调用绑定函数时作为 this 参数传递给目标函数的值。如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

arg1, arg2, ...

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

返回值

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

使用

  1. 保存函数上下文

在回调函数中使用bind来绑定this,以免丢失函数上下文。

function log(){
  console.log(this.name);
}
const obj = {name: 'Jack'};
setTimeout(log.bind(obj), 1000);
  1. 部分应用函数

通过bind()可以绑定函数的部分参数,返回一个新的函数。

function mul(a, b){
  return a * b;
}
const double = mul.bind(null, 2);
double(3); // 6
double(4); // 8
  1. 创建高阶函数

bind()允许你通过给一个函数绑定作用域来定制它的行为。

function greet(prefix, name){
  console.log(prefix + ' ' + name);
}
const greetHello = greet.bind(null, 'Hello');
greetHello('John'); // Hello John
  1. 箭头函数没有this,可以通过bind()来获取外层this
const person = {
  name: 'Jack',
  sayName: () => {
    console.log(this.name); // undefined
    setTimeout(() => {
      console.log(this.name); // undefined
    }, 100);
  }
};
person.sayName(); 
// 解决方案
const person = {
  name: 'Jack',
  sayName: function() {
    console.log(this.name);
    
    setTimeout(function() {
      console.log(this.name); 
    }.bind(this), 100); 
  }
};
person.sayName();

手动实现

🏅关键点:bind 不会立即执行函数,需要返回一个改变了上下文的函数

Function.prototype.myBind = function (context, ...bindArgs) {
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  // 处理边界条件
  context = context || {}
  const symbolKey = Symbol()
  target[symbolKey] = this
  return function (...innerArgs) { // 返回一个函数
    // bindArgs和innerArgs都是一个数组,解构后传入函数
    const res = context[symbolKey](...bindArgs, ...innerArgs)
    // 这里千万不能销毁绑定的函数,否则第二次调用的时候,就会出现问题
    return res
  }
}

三者的比较

applycallbind 这三个方法都可以改变函数内部的 this 指向,但有以下区别:

  1. 传参方式不同
  • apply 接收两个参数,第一个是指定的 this 值,第二个是数组或类数组对象。
  • call 接收多个参数,第一个是指定的 this 值,后面的是函数的参数。
  • bind 传参方式和 call 一样,只是它返回一个绑定了 this 的函数。
  1. 是否立即执行函数
  • applycall 会立即执行函数。
  • bind 不会立即执行,而是返回一个改变了 this 指向的函数。
  1. 是否返回结果
  • applycall 会立即执行,所以会有返回结果。
  • bind 只是绑定 this,不会执行,所以不会有返回结果,调用绑定后的函数才会返回结果。
  1. 独有功能
  • apply 可以把数组作为函数的参数。
  • bind 可以绑定函数的 this 和部分参数,返回绑定后的函数。

所以主要区别是:

  • callapply 立即执行,bind 返回绑定后的函数
  • callbind 接收参数列表,apply 接收数组
  • bind 可以绑定 this 和部分参数

彩蛋🤩

  • 利用 apply 来实现 call
Function.prototype.myCall = function(context, ...args) {
  context = context || window;
  // 使用 apply 来调用函数
  args = args ? [...args] : [];
  return this.apply(context, args);
}
  • 利用 call 实现 apply
Function.prototype.myApply = function(context, args) {
  // 判断 args 是否为数组
  if(!Array.isArray(args)) {
    throw new TypeError('Second argument must be an array');
  }
  // 展开数组为参数
  args = [...args];
  // 使用 call 实现 apply
  return this.call(context, ...args);
};
  • 利用apply 实现bind
Function.prototype.myBind = function(context, ...bindArgs) {
  const fn = this;
  return function(...callArgs) {
    // 合并 bind 参数和调用参数
    const args = [...bindArgs, ...callArgs];
    // 使用 apply 改变 this 指向并传入参数
    return fn.apply(context, args);
  }
}
相关文章
new bind apply call instanceof 等笔记
new bind apply call instanceof 等笔记
35 0
|
5天前
|
JavaScript 前端开发 开发者
call、bind、apply区别
【10月更文挑战第26天】`call`、`bind` 和 `apply` 方法在改变函数 `this` 指向和参数传递方面各有特点,开发者可以根据具体的需求和使用场景选择合适的方法来实现更灵活和高效的JavaScript编程。
12 1
|
6月前
call\apply\bind详解
call\apply\bind详解
32 0
bind、call、apply 区别
bind、call、apply 区别
76 0
call、apply、bind笔记
call、apply、bind笔记
62 0
apply、bind和call
apply、bind和call
87 0
|
JavaScript 前端开发
一文搞定this、apply、call、bind
一文搞定this、apply、call、bind
|
前端开发