🙌 如文章有误,恳请评论区指正,谢谢!
❤ 写作不易,「点赞」+「收藏」+「转发」 谢谢支持!
背景
近期在研究高阶函数封装的过程中,看到
fn.apply(this, arguments)
的出镜率非常高,而如果对于不了解该用法的初学者来说,可能直接写成fn()
是更好理解的,但为什么不局限于此,而是要再上升到fn.apply(this, arguments)
的使用呢?此篇文章便来带你一探究竟~
原文链接:对 fn.apply(this, arguments) 的使用还在疑惑?快进来看看它的设计含义及常见使用场景吧~
apply 的基本介绍
我们先来看看 MDN - Function.prototype.apply 对于 apply 函数的介绍:
其实简单来说就是,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 用一下
代码调用如下图:
括号中的 arguments 含义
我们先来看看 MDN - arguments 对于 arguments 类数组的介绍:
arguments
是一个对应于传递给函数的参数的类数组对象,即我们可以不用显式地在函数入参处声明变量,例如 func1(num1, num2, s3)
来获得变量,而是通过 arguments 获得参数数组即可
const func1 = function() {
console.log("传入函数的参数", arguments);
}
func1(1, 2, "test");
代码调用如下图:
开始介绍 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)
调用的结果如图:
可以看到 test()
中打印出的 this
指向是 obj1
,即我通过 obj1
来调用自己的 func1
函数,通过 fn.apply(this, arguments)
可以保留我的 this
指向,让我在第三方函数中调用时的 this
作用域仍保留是 obj1
!
而 fn()
调用的结果如图:
可以看到 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的原型和原型链(新手懵懂想学会原型链?看这篇文章就足够啦!!!)》