「JS硬气功👊」闭包闭了什么包?(JS函数闭包一网打尽)

168 阅读3分钟

Hi!这里是面试遇到闭包的JustHappy,想想当时回答的并不好,所以就让我们一起回顾一下吧🚀🚀

image.png

简单的来说JS中的闭包就一句话!

对于函数内自由变量的查找,是要在函数定义的地方向外查找,而不是函数调用或者返回的地方查找!!!

概念闭包的定义:闭包是指一个函数和其周围的状态(词法环境)的组合。闭包让你可以从内部函数访问外部函数作用域中的变量,即使外部函数已经执行完毕。

一些概念的补充

什么是作用域

作用域就是变量可以被调用的区域

JS作用域有以下三种:

  • 全局作用域:在全局上下文中定义的变量。
  • 函数作用域:在函数中定义的变量
  • 块级作用域:由letconst声明的变量,其作用域限制在它所在的{}

什么是自由变量?

自由变量是指该在函数中未被声明但是被调用,那么我们就会向函数之外的作用域查找该变量,就像下面这样

let a1 = 0
function fn1(){
    console.log(a1)
}

自由变量会一层一层的作用域查找,如果到全局作用域还没找到,那就会报错xx is undefine

闭包简单实例

当函数作为返回值

function set(){
    const a = 100
    return function(){ 
        console.log(a)
    }
}

const fn = set()
const a = 200
fn()
// 输出:100

很显然还是那句话:对于函数内自由变量的查找,是要在函数定义的地方向外查找,而不是函数调用或者返回的地方查找!!!

fn函数被调用时,它会查找其作用域链中的变量a。由于fn函数是在set函数的作用域中定义的,它会优先查找set函数内部的变量a,而不是全局变量a

当行数作为参数传递

const p = (fn()) => {
    const a = 200
    fn() 
}

const a = 100
function fn(){
    console.log(a)
}
//输出:100

很显然还是那句话:对于函数内自由变量的查找,是要在函数定义的地方向外查找,而不是函数调用或者返回的地方查找!!!

fn函数访问的是全局变量a,而不是p函数内部的a。这确实体现了作用域链的查找机制,但并不是闭包的典型例子。

闭包实战!手写防抖、节流

防抖

防抖主要是为了防止某一行为被高频触发,其核心是维护一个计时器timer

function debounce(fn,delay){
    let timer;
    return function(...args){
        clearTimeout(timer)
        timer = setTimeout(() => fn.apply(this,args),delay)
    }
}

// 用法如下:以防止输入触发搜索为例,以下是等用户输入完500毫秒再执行搜索
const handleInput = debounce((e) => { console.log("哈哈你别急,会等你输入完的") } , 500)
document.getElementById("search").addEventListener("input",handleInput)

闭包的作用: timer变量在debounce内部被保存,确保多次触发时候可以清除上一次的定时器

节流

节流是为了限制函数的执行频率,比如说我们写了个监听滚动事件的函数,如果不使用节流,就会导致每下拉1px都会触发一次函数,这显然是没有必要的

节流的核心也是维护一个计时器

function throttle(fn,delay){
    let lastTime = 0 
    return function(...args){
        const new = Date.now()
        if(now - lastTime > delay){ //计算时间差,如果大于节流限制时间就触发
            fn.apply(this,args)
            lastTime = now
        }
    }
}

// 用法:以限制滚动触发为例
const handlScroll = throttle((e) => { console.log("被触发")},1000)
window.addEventListener("scroll", handlScroll)

闭包的作用: 保存lastTime变量