• 作者:老汪软件技巧
  • 发表时间:2024-11-11 11:03
  • 浏览量:

如文章有误,恳请评论区指正,谢谢!

❤ 写作不易,「点赞」+「收藏」+「转发」谢谢支持!

背景

近期在研究高阶函数封装的过程中,看到 fn.apply(this, arguments) 的出镜率非常高,而如果对于不了解该用法的初学者来说,可能直接写成 fn() 是更好理解的,但为什么不局限于此,而是要再上升到 fn.apply(this, arguments) 的使用呢?此篇文章便来带你一探究竟~

apply 的基本介绍

我们先来看看 对于 apply 函数的介绍:

1.jpg

其实简单来说就是,apply 函数可以改变使用者的身份,例如如下例子中,obj1 对象有一个方法 func1,那想调用该方法就直接 obj1.func1() 即可。而 obj2 对象中没有方法 func1 但又想使用该方法,那可以通过 apply 来“借用”,即 obj1.func1.apply(obj2),此时就相当于括号中的 obj2 来调用该函数了!

const obj1 = {
    func1: function() { console.log(this, this.data) },
    data: "obj1"
}
const obj2 = {
    data: "obj2"
};
obj1.func1();
obj1.func1.apply(func2); // 借 obj1 的 func1 用一下

代码调用如下图:

image.png

括号中的 arguments 含义

我们先来看看 对于 arguments 类数组的介绍:

4.jpg

arguments是一个对应于传递给函数的参数的类数组对象,即我们可以不用显式地在函数入参处声明变量,例如 func1(num1, num2, s3) 来获得变量,而是通过 arguments 获得参数数组即可

const func1 = function() {
    console.log("传入函数的参数", arguments);
}
func1(1, 2, "test");

代码调用如下图:

image.png

开始介绍 fn.apply(this, arguments)

先举一个代码的场景作为例子:

const obj1 = {
    func1: function(fn) {
        fn.apply(this, arguments);
        // fn();
    }
}
const test = function() { console.log(this, arguments) };
obj1.func1(test, 1, 2);

1. 由此我们可以大概推出什么逻辑?

当我们通过 obj1.func1() 调用函数时,此时 func1 中的 this 指向即为 obj1,而如果我传参进 func1 时,arguments 即为这些传入的参数。

2. 那跟直接 fn() 调用的区别在哪呢?

作用域的区别!

fn.apply(this, arguments) 调用的结果如图:

image.png

可以看到 test() 中打印出的 this 指向是 obj1,即我通过 obj1 来调用自己的 func1 函数,通过 fn.apply(this, arguments) 可以保留我的 this 指向,让我在第三方函数中调用时的 this 作用域仍保留是 obj1!

而 fn() 调用的结果如图:

image.png

用疑惑写一段话__疑惑用什么词搭配

可以看到 test() 中打印出的 this 指向变成了 Window,this 作用域消失了,变成 Window 了!

原因:如果你直接调用 fn(),而 fn 即 test 函数定义在了 Window 作用域中,因此相当于由 Window 调用,因此 test 函数内部指向就变成了 Window。

3. 结论

因此,我们可以知道,通过使用 fn.apply(this, arguments) 代替 fn(),我们可以保留函数作用域,使得原本谁调用该函数,那经过中间函数包裹处理后,还是他来继续调用该函数。

常见使用场景

当你需要通过高阶函数来二次处理一个普通函数时,因为高阶函数特性就是经过高阶函数处理后,不改变原函数的任何性质,包括函数作用域和函数所传参数,而防抖和节流就是常见使用场景之二。

1. 防抖(debounce) 函数

function debounce (fn, wait, immediate) {
  let timer;
  return function () {
    // 谁调用返回的这个函数,那 this 指向就是指向谁
    if (immediate) fn.apply(this, arguments); // 立即执行
    if (timer) clearTimeout(timer); // 如果debounce不再触发,那么 setTimeout 过 wait 会自动执行,但如果再次触发了就要清空计时器,重新计时
    
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer && clearTimeout(timer);
      timer = null;
    }, wait);
    
  }
}
function log () {
  console.log("a");
  console.log(this); // 保留了this指向,比如只有 obj 中才有 a,才能正确打印
}
const obj = {
  a: 1,
  debounceObj: debounce(log, 2000) // 绑定在了obj内
}
const debounceWindow = debounce(log, 350, false);
for (let i = 1; i <= 10; i++) {
  setTimeout(obj.debounceObj, i * 100); // 相当于每 100ms 都会触发一次防抖函数,都会导致 350ms 的计时器重新计时,之后最后一次会打印出'a'     
}

2. 节流(throttle) 函数

let count = 0;
function throttle (fn, wait) {
  let prev = new Date();
  const that1 = this;
  return function () {
    // 谁调用返回的这个函数,那 this 指向就是指向谁
    let now = new Date();
    // 如果超出了需等待的 wait,那就可以执行啦总算
    if (now - prev > wait) {
      // fn();
      fn.apply(this, arguments);
      prev = new Date();
    }
  }
}
// const throttleTest = throttle(test, 2000);
const obj = {
  a: 1,
  throttleTestObj: throttle(test, 2000) // 绑定在了obj内
}
const throttleTestWindow = throttle(test, 2000); // 绑定在了window
function test () {
  count++;
  console.log("打印this环境", this, count); // 保留了this指向,比如只有 obj 中才有 a,才能正确打印
};
setInterval(() => {
  obj.throttleTestObj();
}, 500)

通过在防抖和节流函数中使用 fn.apply(this, arguments),保留了原函数 fn 正确的 this 指向,比如只有 obj 中才有 a,因此才能正确打印,即没改变作用域!

最后

我是 Smoothzjc,致力于产出更多且不仅限于前端方面的优质文章

大家也可以关注我的公众号 @ Smooth前端成长记录,及时通过移动端获取到最新文章消息!

写作不易,「点赞」+「收藏」+「转发」谢谢支持❤

往期推荐

《requestAnimationFrame 与 setInterval 究竟如何选择?》

《拒绝死记硬背!清晰思路讲透 控制并发数、Promise.all、Promise.race 的实现逻辑》

《手把手教前端从0到1通过 Node + Express 开发简易接口,项目开发+部署服务器(亲身痛苦经历)》

《都2022年了还不考虑来学React Hook吗?6k字带你从入门到吃透》

《一份不可多得的 Webpack 学习指南(1万字长文带你入门 Webpack 并掌握常用的进阶配置)》

《通过 React15 ~ 17 的优化迭代来简单聊聊 Fiber》

《【offer 收割机之面试必备】一篇非常全面的 从 URL 输入到页面展现的全过程 精华梳理》

《【offer 收割机之手写系列】10分钟带你掌握原理并手写防抖与节流的立即/非立即执行版本》

《Github + hexo 实现自己的个人博客、配置主题(超详细)》

《带你3分钟掌握常见的水平垂直居中面试题》

《【建议收藏】长达万字的git常用指令总结!!!适合小白及在工作中想要对git基本指令有所了解的人群》

《浅谈javascript的原型和原型链(新手懵懂想学会原型链?看这篇文章就足够啦!!!)》