文章目录
1.闭包是什么?作用是什么?有哪些特性?有什么使用场景和注意事项呢?
1、闭包是什么
闭包是由函数以及声明该函数的词法环境组合而成的。这个说法来源于MDN-闭包。
还有种说法:闭包是指有权访问另外一个函数作用域中的变量的函数。
function foo() {
var a = 1;
return function() {
console.log(a);
}
}
var bar = foo();
bar();
foo()函数的执行结果返回给bar,而此时变量a仍在使用,还没被销毁,然后执行bar()函数。这样我们就能在外部作用域访问到函数内部作用域的变量。这个就是闭包。
闭包产生的必要条件是:
- 存在函数嵌套;
- 嵌套的内部函数必须引用在外部函数中定义的变量;
- 嵌套的内部函数必须被执行。
2.闭包的作用
- 可以读取函数内部的变量
- 可以使变量的值长期保存在内存中,生命周期比较长。
- 可用来实现JS模块
3. 闭包的特性
- 每个函数都是闭包,函数能够记住自己定义时所处的作用域,函数走到了哪,定义时的作用域就到了哪。
- 内存泄漏
内存泄漏就是一个对象在你不需要它的时候仍然存在。所以不能滥用闭包。当我们使用完闭包后,应该将引用变量置为null。
function outer(){
var num = 0;
return function add(){
num++;
console.log(num);
};
}
var fun1 = outer();
fun1(); // 1
fun1(); // 2 [没有被释放,一直被占用]
var fun2 = outer();
fun2(); // 1 [重新引用函数时,闭包是新的]
fun2(); // 2
4. 使用场景
任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的
- 实现点击第几个button就输出几
<button>1</button>
<button>2</button>
for(var i = 0; i < button.length; i++) {
(function(item){
button[item].onclick = function() {
console.log(item+ 1);
}
})(i)
}
- 柯里化函数
柯里化的目的在于避免频繁调用具有相同参数函数的同时,又能够轻松的重用
function func(fn, len = fn.length) {
return _func(fn, len)
}
function _func(fn, len, ...arg) {
return function (...params) {
let _arg = [...arg, ...params]
if (_arg.length >= len) {
return fn.apply(this, _arg)
} else {
return _func.call(this, fn, len, ..._arg)
}
}
}
let fn = func(function (a, b, c, d, e) {
console.log(a + b + c + d + e)
})
fn(1, 2, 3, 4, 5)
fn(1, 2)(3, 4, 5)
fn(1, 2)(3)(4)(5)
fn(1)(2)(3)(4)(5)
- 节流防抖
// 节流
function throttle(fn, timeout) {
let timer = null
return function (...arg) {
if(timer) return
timer = setTimeout(() => {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
// 防抖
function debounce(fn, timeout){
let timer = null
return function(...arg){
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, timeout)
}
}
其他
例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期
5. 注意事项
若不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。
常问面试题:
let x = 5;
function fn(x) {
return function(y) {
console.log(y + (++x)); //14
}
}
let f = fn(6);
f(7);
console.log(x); //5
//14
//5
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); // 3
data[1](); // 3
data[2]() // 3
i 是全局的 i,共用一个作用域,当函数被执行的时候这时的 i=3,导致输出的结构都是3。
可以用闭包改善上面的写法达到预期效果
1. 自执行函数和闭包
var data = [];
for (var i = 0; i < 3; i++) {
(function(j){
setTimeout( data[j] = function () {
console.log(j);
}, 0)
})(i)
}
data[0]();
data[1]();
data[2]()
2. 使用 let
var data = [];
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]()
let 具有块级作用域,形成的3个私有作用域都是互不干扰的。
2. 浏览器有哪几种缓存,各种缓存的优先级是什么样的?
在浏览器中,有以下几种常见的缓存:
- 强制缓存:通过设置 Cache-Control 和 Expires 等响应头实现,可以让浏览器直接从本地缓存中读取资源而不发起请求。
- 协商缓存:通过设置 Last-Modified 和 ETag 等响应头实现,可以让浏览器发送条件请求,询问服务器是否有更新的资源。如果服务器返回 304 Not Modified 响应,则表示客户端本地缓存仍然有效,可直接使用缓存的资源。
- Service Worker 缓存:Service Worker 是一种特殊的 JS 脚本,可以拦截网络请求并返回缓存的响应,以实现离线访问和更快的加载速度等功能。
- Web Storage 缓存:包括 localStorage 和 sessionStorage。localStorage 用于存储用户在网站上的永久性数据,而 sessionStorage 则用于存储用户会话过程中的临时数据。
这些缓存的优先级如下:
- Service Worker 缓存:由于其可以完全控制网络请求,因此具有最高的优先级,即使是强制缓存也可以被它所覆盖。
- 强制缓存:如果存在强制缓存,并且缓存没有过期,则直接使用缓存,不需要向服务器发送请求。
- 协商缓存:如果强制缓存未命中,但协商缓存可用,则会向服务器发送条件请求,询问资源是否更新。如果服务器返回 304 Not Modified 响应,则直接使用缓存。
- Web Storage 缓存:Web Storage 缓存的优先级最低,只有在网络不可用或者其他缓存都未命中时才会生效。