← 返回首页
深入理解闭包
发表时间:2022-08-26 17:56:23
深入理解闭包

闭包是Javascript语言的一个难点,也是它的特色,很多高级应用都是依靠闭包实现。

1.什么是闭包?

最通俗的解释:函数嵌套函数,内部函数就是闭包。换句话说就是定义在一个函数内部的函数。

例如:

function foo(){
    let name = "jason"; //局部变量
    console.log('foo()...')

    //fn就是闭包
    function fn(){
       console.log(name)
       console.log('fn()...')
    }
    fn();
}

foo();

运行结果:

foo()... 
jason    
fn()...  

需要强调的是: 闭包不一定非要返回内部函数,无论fn是否返回这个闭包已经形成了,这里表示的是一种状态。什么状态呢?这个状态就内部函数可以访问被创建时所处的上下文环境,强调的是内部函数可以访问外部函数的局部变量。

2.闭包的意义 - 可以在函数的外部访问到函数内部的局部变量。 - 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。

上面的例子,在foo函数外是无法访问name局部变量的。

function foo(){
    let name = "jason"; //局部变量
    console.log('foo()...')
    //fn就是闭包
    function fn(){
       console.log(name)
       console.log('fn()...')
    }
    fn();
}

foo();

//node环境下:error,undefined, 
//浏览器环境下:空字符串,默认绑定了window.name
console.log(name);  

foo函数返回内部函数后。

function foo(){
    let name = "jason"; //局部变量
    console.log('foo()...')
    function fn(){
       console.log('fn()...')
       return name;
    }
    return fn;
}

let name = foo()();
console.log(name);

运行结果:

foo()...
fn()...
jason

这里需要重点强调的是闭包是通过持有外部函数变量的引用,从而实现这些变量始终保存在内存中,并且可以维持状态的改变。

看下面的例子:

function foo(){
    let num = 100; //局部变量
    console.log('foo()...')
    num++;
    function fn(){
        console.log('fn()...')
        return num;
    }
    return fn;
}

let f = foo();
console.log(f());
foo();
console.log(f())

运行结果:

foo()... 
fn()...  
101      
foo()... 
fn()...  
101  

虽然foo()函数执行了两遍,但是num++并不是在闭包之内,并不能改变闭包函数里num引用所指向的内容发生改变,所以两次输出都是101。 改写如下:

function foo(){
    let num = 100; //局部变量
    console.log('foo()...')

    function fn(){
        num++;
        console.log('fn()...')
        return num;
    }
    return fn;
}

let f = foo();
console.log(f());
foo();
console.log(f())

运行结果:

foo()...
fn()...
101
foo()...
fn()...
102

由于num++出现在闭包函数里面,所以每次执行foo()函数都会改变闭包函数里面num引用指向的内容发生改变,因此第二次输出102。

但是,这里有一个陷阱值得大家特点注意:函数体包裹的每块代码在每次执行都会产生一个新作用域,然后在函数体执行完后被释放,我们平时在执行这一个过程的时候常常忽略了这样一个事实,闭包也不例外,也就是说,每次执行foo()其实会产生一个新的闭包作用域,至于这个新作用域是否使用取决于你是否接收了这个返回值。

代码改写如下:

function foo(){
    let num = 100; //局部变量
    console.log('foo()...')
    function fn(){
        num++;
        console.log('fn()...')
        return num;
    }
    return fn;
}

let f = foo();
console.log(f());
f = foo(); //产生一个新的闭包作用域
console.log(f())

运行结果:

foo()...
fn()...
101
foo()...
fn()...
101

第二次执行foo()函数后,f已经指向了新的作用域,所以两次输出都是101。我们可以证明原来的f 作用域还存在。代码改写如下:

function foo(){
    let num = 100; //局部变量
    console.log('foo()...')
    function fn(){
        num++;
        console.log('fn()...')
        return num;
    }
    return fn;
}

let f = foo();
console.log(f());
let temp = f;
f = foo(); //产生一个新的闭包作用域
console.log(f())
console.log(temp());

运行结果:

foo()... 
fn()...  
101      
foo()... 
fn()...  
101      
fn()...  
102  

3.闭包的应用

1)实现模块化

mymoudule.js

let myModule = (function () {
    function add(x, y) {
        return x + y;
    }
    return {
        add: add
    }
})();

export default myModule;

在页面中引入模块。

<script type="module">
    import myModule from "./mymodule.js";
    console.log(myModule.add(10,7));
</script>

2)防抖和节流

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<div>
    我需要的是,首先,输入框的结果只出现一次,是在我键盘抬起不在输入后的1s之后
</div>
<input type="text" id="input"/>
<script>
    let input = document.querySelector('#input')
    // 防抖函数
    function debounce(delay){
        let timer
        return function (value){
            clearTimeout(timer)
            timer = setTimeout(function(){
                console.log(value)
            },delay)
        }
    }
    let res = debounce(1000)
    input.addEventListener('keyup',(e)=>{
        res(e.target.value)
    })
</script>
</body>
</html>

3)单例

function Person(){
    this.age=18;
}

//单例模式
const singleton=(function (){
    let instance=null;//单例
    return function(){
        if(!instance)
            instance=new Person();

        return instance;
    }
})();

let p1 = new Person();
let p2 = new Person();

console.log(p1===p2); //false
let p3 = singleton();
let p4 = singleton();

console.log(p3===p4) //true

总而言之,闭包函数的实际用途还有很多,在这里我就只简单列举以上应用。

小结: 1. 闭包就是定义在一个函数内部的函数。 2. 通过闭包可以在函数的外部访问到函数内部的局部变量。让这些局部变量始终保存在内存中,不会随着函数的结束而自动销毁。 3. 闭包的实际应用有:模块化,防抖节流函数、单例等等。