这是我参与8月更文挑战的第12天,活动详情查看8月更文挑战
前言
“你在实际开发中,对闭包有哪些应用?”,面试官抛出这个问题的目的,主要是考察你编码的熟练度和知识的广度。今天就带大家了解下,闭包有哪些应用,开始吧。
模拟私有变量的实现
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。
我们先来看下一个 User 类
class User {
constructor(username, password) {
// 用户名
this.username = username;
// 密码
this.password = password;
}
login() {
// 使用 fetch 进行登录请求,请求的时候需要带上带上用户名和密码
// 登录处理逻辑相关代码。。。
console.log('username', username);
console.log('password', password);
}
}
通过 User 类,创建的每一个实例,都具备 login 这个函数:
let user = new User('追梦玩家', 'dreamer123');
console.log(user.login); // 输出结果?
console.log(user); // 输出结果?
输出结果,看截图
password 暴露在外面,相当于其他人知道只要有 user 这个对象,就可以获取到你的 password,或者修改。
在软件世界中,只要是依赖人的意志才可以确保其安全稳定的东西,都是不可靠的。
我们要从代码层面去保护 password。像 password 这样的变量,我们希望它仅在对象内部生效,无法从外部触及,这样的变量,就是私有变量。
我们的思路,就是把私有变量用函数作用域保护起来,形成一个闭包,实现在内部可以拿到、外部拿不到的效果。 我们改造下代码
const User = (function() {
// 定义私有变量 _password
let _password;
class User {
constructor(username, password) {
// 用户名
this.username = username;
// 密码
_password = password;
}
login() {
// 使用 fetch 进行登录请求,请求的时候需要带上带上用户名和密码
// 登录处理逻辑相关代码。。。
// 这里我们增加一行 console,为了验证 login 里仍可以顺利拿到密码
console.log(this.username, _password);
}
}
return User;
})();
let user = new User('追梦玩家', 'dreamer123');
console.log(user.username);
console.log(user.password);
console.log(user._password);
console.log(user.login());
我们看到打印 user.password 和 user._password 都是 undefined,执行 user.login 函数,可以输出this.username 和 _password,说明已经实现了私有变量的效果了。
柯里化
我们看看维基百科中对柯里化的定义:
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
其实就是说,柯里化是把接受 n 个参数的 1 个函数改造为只接受 1个参数的 n 个互相嵌套的函数的过程。也就是 fn(a, b, c) 会变成 fn(a)(b)(c)。
普通的 add 函数
function add (a, b) {
return a + b;
}
add(1, 2);
改造成柯里化函数
function add (x) {
return function(y) {
return x + y;
}
}
var increment = add(1);
var addTen = add(10);
console.log(increment(2));
// 3
console.log(addTen(2));
// 12
console.log(add(1)(2));
// 3
这里定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。所以说 bind 本身也是闭包的一种使用场景。
通过后者这种形式,我们可以选择性地决定是否要 “记住” prefix、type,从而即时地生成更加符合我们预期的、复用程度更高的目标函数。
偏函数
柯里化是将一个 n 个参数的函数转换成 n 个单参数函数。你有 10 个入参,就得嵌套 10 层函数,且每层函数都只能有 1 个入参。它的目标就是把函数的入参拆解为精准的 n 部分。
偏函数应用相比之下就 “随意” 一些了。偏函数是说,固定你函数的某一个或几个参数,然后返回一个新的函数(这个函数用于接收剩下的参数)。你有 10 个入参,你可以只固定 2 个入参,然后返回一个需要 8 个入参的函数 —— 偏函数应用是不强调 “单参数” 这个概念的。它的目标仅仅是把函数的入参拆解为两部分。
我们现在是一家电商公司,旗下有多个电商站点。为了确保商品名的唯一性,我们考虑使用 prefix(一个标识不同站点的前缀字符串)、 type(商品类型)、name(商品原本名称)三个字符串拼接的方式来为商品生成一个完整版名称。 原有的函数形式与调用方法:
function generateName(prefix, type, itemName) {
return prefix + type + itemName
}
// 调用时一口气传入3个入参
var itemFullName = generateName('大卖网', '母婴', '奶瓶')
偏函数应用改造:
function generateName(prefix) {
return function(type, itemName) {
return prefix + type + itemName
}
}
// 把3个参数分两部分传入
var itemFullName = generateName('大卖网')('母婴', '奶瓶')
函数防抖、节流
debounce 与 throttle 是开发中常用的高阶函数,作用都是为了防止函数被高频调用,换句话说就是,用来控制某个函数在一定时间内执行多少次。
使用场景
比如绑定响应鼠标移动、窗口大小调整、滚屏等事件时,绑定的函数触发的频率会很频繁。若稍处理函数微复杂,需要较多的运算执行时间和资源,往往会出现延迟,甚至导致假死或者卡顿感。为了优化性能,这时就很有必要使用 debounce 或 throttle了。
防抖与节流的区别
- 防抖 (debounce) :多次触发,只在最后一次触发时,执行目标函数。
- 节流(throttle):限制目标函数调用的频率,比如:1s内不能调用2次。
参考
- 05 闭包的应用
- 第165题:闭包的使用场景,使用闭包需要注意什么
- 深入理解JavaScript闭包之闭包的使用场景
- 闭包实际场景应用
- 闭包及闭包的应用
- 面试官:说说你对闭包的理解?闭包使用场景
- 什么是闭包?闭包的作用是什么?
- JavaScript系列之闭包
文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你或者喜欢,欢迎点赞和关注。