js闭包之——相信我,这次真是手把手教

一、理解与记忆核心

闭包的含义:闭包是一个访问了其他函数内部作用域的函数

产生闭包的原因:当前函数内保持对上层作用域的引用(一般表现为函数内返回另一个函数)

二、闭包的理解与实现

首先

根据【闭包是一个访问了其他函数内部作用域的函数】的定义

在不知道闭包的情况下,当我们想要在函数外使用函数内部的变量时,我们通常会想到什么?

1.全局变量!!

将函数内的局部变量变成全局变量,
在全局作用域中声明,在函数中赋值,这样,只要调用过该函数,就可以在函数外部取到想要的值

//全局变量
var a;//变量声明,let和const声明的变量充其量只能算script块下的,没有挂到window全局,因此,这里我还是改成var
function fn(){
    a=20;//变量赋值
}
fn();//函数调用
console.log(window.a);//取到函数内作用的结果
复制代码

为了图片展示方便,以下图片中我还是使用node环境运行,但运行结果保证与浏览器中一致
image.png
还有一个更好玩的
image.png
每次调用都相当于对同一个全局变量进行了+1的操作,可以实现累积的效果

但是!!
很多时候,我们并不想声明一个全局变量,因为每次访问a都相当于访问window.a,这样看起来是不是感觉太占用window变量的资源了,
如果只想用局部变量但又想在函数外访问呢?

2.不想使用全局变量

如果直接在外部使用局部变量,结果就报错了!a is not defined

// 局部变量
function fn(){
    let a=30;
}
fn();
console.log(a);
复制代码

那应该怎么办呢?
两种思路,

法一:将a返回,把a从函数里抛出,让外面的aa来接住它!

法二:暴露一个访问器 ( 函数 ),让别人可以【间接访问】

2.1.直接将局部变量返回

将a返回,把a从函数里抛出,让外面的aa来接住它!

function fn(){
    let a = 40;
    return a;
}
let aa = fn();
console.log(aa);
复制代码

除此之外,我还可以对a进行一番play之后再返回

image.png

2.2.暴露一个访问器,让外部间接访问

        function fn(){
            let a=40;
            // 注意,get和set不要加let或var,这样才相当于挂载到了全局对象上,否则外部是无法访问get和set的
            //这里的get相当于一个全局变量,被挂载到了全局对象上
            get = function(){
                return a;
            };
        }
        fn();
        let aa = get();
        console.log(aa);
        console.log(window.get());
复制代码

image.png

image.png

同样,可以对a进行一番play之后再返回

image.png
但是,如果我想多次对a进行该操作呢?

3.对该局部变量进行多次累计操作

同样对两种方法进行测试

3.1.直接返回

image.png
从图中结果可以看出,由于每次函数执行完毕时,js会进行垃圾回收,将作用域清空回收,每次a都被重新赋值,因此不管我调用多少次,都只会返回一次调用的结果
如何解决呢?
通过1中全局变量的例子可以得到一些启发
image.png
如果我们在(父)函数中有一个局部变量,并且(父)函数中有另一个(子)函数来访问该局部变量,那我们每调用一次(子)函数,由于(子)函数内有对其外部的a变量的引用,就可以实现该局部变量a的累积变动,为了能在(父)函数外部能操作,将操作的(子)函数直接返回

image.png
而这,就是我们的主角闭包了,因此才说一般闭包表现为一个函数中返回另一个函数

3.2.暴露一个访问器,让外部间接访问

image.png
为什么这样就可以呢?其实这种方式也属于闭包的一种,因为这里的get和set也满足了闭包【访问了其他函数内部作用域】的定义
所以,也不一定是return一个函数才能产生闭包,只是大部分情况是这样

三、闭包的应用场景

1、例如定时器、事件监听以及Ajax请求等这类使用回调函数的,基本都利用到了闭包,使用定时器的例子,如防抖/节流:

// 防抖
const debounce = (fn,delayTime) => {
  let timerId, result
  return function(...args) {
    timerId && clearTimeout(timerId)
    timerId = setTimeout(()=>result=fn.apply(this,args),delayTime)
    return result
  }
}
复制代码
// 节流
const throttle = (fn, delayTime) => {
  let timerId
  return function(...args) {
    if(!timerId) {
      timerId = setTimeout(()=>{
        timerId = null
        return result = fn.apply(this,args)
      },delayTime)
    }
  }
}
复制代码

2、IIFE(立即执行函数),这种函数比较特别,它拥有独立的作用域,不会污染全局环境,但是同时又可以防止外界访问内部的变量,所以很多时候会用来做模块化或者模拟私有方法
举个例子:

var global = '全局变量'
let Anonymous = (function() {
  var local = '内部变量'
  console.log(global)    // 全局变量
})()
console.log(Anonymous.local)   // local is not defined

=======分割线==============

var global = '全局变量'
let Anonymous = (function() {
  var local = '内部变量'
  console.log(global)    // 全局变量
  return {
    afterLocal: local
  }
})()
console.log(Anonymous.afterLocal)   // 内部变量
复制代码

3.计时器

function counter2(){
    let time = 0;
    return function(){
        time++;
        console.log(time);
        if(time>10){
            clearInterval(timer);
        }
    }
}
let count = counter2();
count();
count();
count();
复制代码

4.如何在浏览器中查看闭包

        function fn1(){
            let a=11;
            let b="bbb";
            return function fn2(){
                let c="c";
                console.log(c);
                console.log(a);
            }
        }
        fn1()();
复制代码

2797BE02-35F9-4AA5-ACCB-28D9DAA4F8A2.png
当运行到fn2内部时,就产生了闭包Closure,且里面只有引用到的外部函数变量a

四、总结一下

一、什么是闭包?

访问了其他函数内部作用域的函数

二、产生闭包的原因?

当前函数内保持对上层作用域的引用

三、闭包的优缺点?

优点:

  • 可以让一个变量长期存储在内存中。
  • 避免全局变量的污染。

缺点:

  • 常驻内存,增加内存使用量。
  • 使用不当会很容易造成内存泄露。

五、参考

juejin.cn/post/696642…

zhuanlan.zhihu.com/p/22486908

六、往期精彩

原型与原型链

this指向

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享