定义

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

作用域

理解闭包,首先必须理解变量作用域。前面提到,JavaScript 有两种作用域:全局作用域函数作用域。函数内部可以直接读取全局变量。

var n = 999;

function f1() {
  console.log(n);
}
f1() // 999

上面代码中,函数f1可以读取全局变量n

但是,正常情况下,函数外部无法读取函数内部声明的变量。

function f1() {
  var n = 999;
}

console.log(n)
// Uncaught ReferenceError: n is not defined(

上面代码中,函数f1内部声明的变量n,函数外是无法读取的。

如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数

function f1() {
  var n = 999;
  function f2() {
  console.log(n); // 999
  }
}

上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。

闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大用处有两个:

  • 一个是可以读取外层函数内部的变量
  • 这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在

请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。

function createIncrementor(start) {
  return function () {
    return start++; // start一直存在内存中,不会被释放
  };
}
// 等同于
function createIncrementor(start) {
    function neibu() {
        start++
    }
    return neibu();
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口

为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc)用到了外层变量(start),导致外层函数(createIncrementor)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。

在看一个例子

function add(x){
    return function(y){
        return x + y;
    };
}

var addFun1 = add(4);
var addFun2 = add(9);

console.log(addFun1(2)); //6
console.log(addFun2(2));  //11

原文链接:https://blog.csdn.net/qq_39903567/article/details/115010640

add接受一个参数x,返回一个函数,它的参数是y,返回x+y
add是一个函数工厂,传入一个参数,就可以创建一个参数和其他参数求值的函数。
addFun1和addFun2都是闭包

闭包使用场景

1、原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。

let fun1 = (x) => {
    console.log(x)
}
// 立即执行,失效。 
setTimeout(fun1("zmz2001.com"), 3000) 

let fun2 = (x) => {
    return () => {
        console.log(x)
    }
}
// 闭包解决,正常。
setTimeout(fun2("zmz2001.com"), 3000)

// 使用匿名函数包装,正常。
setTimeout(function() {
    console.log("zmz2001.com")
}, 3000)

2、回调函数

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="">
</head>
<style>
    body{
        font-size: 12px;
    }
    h1{
        font-size: 1.5rem;
    }
    h2{
        font-size: 1.2rem;
    }
</style>
<body>

    <p>哈哈哈哈哈哈</p>
    <h1>hhhhhhhhh</h1>
    <h2>qqqqqqqqq</h2>

    <a href="#" id="size-12">12</a>
    <a href="#" id="size-14">14</a>
    <a href="#" id="size-16">16</a>

<script>
    function changeSize(size){
        return function(){
            document.body.style.fontSize = size + 'px';
        };
    }

    var size12 = changeSize(12);
    var size14 = changeSize(14);
    var size16 = changeSize(16);

    document.getElementById('size-12').onclick = size12;
    document.getElementById('size-14').onclick = size14;
    document.getElementById('size-16').onclick = size16;
    //我们定义行为,然后把它关联到某个用户事件上(点击或者按键)。我们的代码通常会作为一个回调(事件触发时调用的函数)绑定到事件上
</script>
</body>
</html>

3、封装对象的私有属性和私有方法

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25

上面代码中,函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

垃圾回收

待补充...

最后修改:2022 年 07 月 04 日 04 : 36 PM
赏杯咖啡喝 谢谢您~