比较一下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);
  }
}
相关文章
|
2天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1529 4
|
17小时前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
29天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
6天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
521 20
|
2天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
186 2
|
9天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
21天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
480 5
|
8天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
317 2
|
5天前
|
XML 安全 Java
【Maven】依赖管理,Maven仓库,Maven核心功能
【Maven】依赖管理,Maven仓库,Maven核心功能
196 2