首页 > 其他分享 >20. 闭包

20. 闭包

时间:2022-12-10 09:48:00浏览次数:27  
标签:闭包 function 20 函数 console fun1 log

函数可以干什么,对象能干什么,函数对象就能干什么
当做变量,参数,返回值

​ 函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

匿名函数:没有名字的函数
1. 把函数当做一个变量赋值

let fun = function(){
        alert("亲,我来自无名函数");
    }

     document.onclick = function(){
        
     }

2.回调函数:一个被当做函数参数的函数

setInterval(function(){
         console.log("heihei");
     },1000);

3.作为函数的返回值(学习闭包的前置条件)

function fun1() {
  const a = 10;
  return function fun2() {
    console.log(a);
  };
}
fun1();
// 调用外部函数,就能得到内部函数,并用 变量 result 接收
const result = fun1();
// 在 fun1函数的外部,执行了内部函数 fun2,并访问到了 fun2的内部变量a
result(); // 10

函数fun2就被包括在函数fun1内部,这时fun1内部的所有局部变量,对fun2都是可见的。但是反过来就不行,fun2内部的局部变量,对fun1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。既然fun2可以读取fun1中的局部变量,那么只要把fun2作为返回值,我们不就可以在fun1外部读取它的内部变量

自运行:针对于匿名函数,当匿名函数定义时,可以被直接自动调用(学习闭包的前置条件)

    // let f = function(){
    //     console.log("新冠型感冒");
    // } 

    // f();

    // f == function(){
    //     console.log("新冠型感冒");
    // } 

    // f() == function(){console.log("新冠型感冒");}() 

    // 思想正确,语法不对
    //function(){console.log("新冠型感冒");}();

    //自运行的语法:
    //a.和c++写法一样
    (function(){
        console.log("新冠型感冒");
    })(); 

    //b.官方推荐
    (function(){
        console.log("嘿嘿嘿");
    }());

    //c.通过运算符实现
    ! function(){
        console.log("嘿嘿嘿1111");
    }();

    void function(){
        console.log("嘿嘿嘿222");
    }();

闭包的引入

变量根据作用域的不同分为两种:全局变量和局部变量。

  • 函数内部可以访问全局变量和局部变量。

  • 函数外部只能访问全局变量,不能访问局部变量。

  • 当函数执行完毕,本作用域内的局部变量会销毁。

比如下面这样的代码:

function foo() {
    let a = 1;
}

foo();
console.log(a); // 打印报错:Uncaught ReferenceError: a is not defined

上方代码中,由于变量 a 是函数内的局部变量,所以外部无法访问。

但是,在有些场景下,我们就是想要在函数外部访问函数内部作用域的局部变量,那要怎么办呢?这就引入了闭包的概念。

什么是闭包

闭包(closure)的概念

闭包:如果外部作用域有权访问另外一个函数内部局部变量时,那就产生了闭包。这个内部函数称之为闭包函数。注意,这里强调的是访问局部变量

闭包代码举例:

function fun1() {
  const a = 10;
  return function fun2() {
    console.log(a);
  };
}
fun1();
// 调用外部函数,就能得到内部函数,并用 变量 result 接收
const result = fun1();
// 在 fun1函数的外部,执行了内部函数 fun2,并访问到了 fun2的内部变量a
result(); // 10

打印结果:

10

上方代码中,外部作用域(即全局作用域) 访问了函数 fun1 中的局部变量,那么,在 fun1 中就产生了闭包,函数 fun1是闭包函数。

全局作用域中,并没有定义变量a。正常情况下作为函数内的局部变量 a,无法被外部访问到。但是通过闭包,我们最后还是可以在全局作用域中拿到局部变量 a 的值。

注意,闭包函数是fun1,不是fun2。fun2在这里的作用是让全局作用域访问到变量a,fun2只是一个桥梁。

闭包的生命周期

  1. 产生:内部函数fun1被声明时(即被创建时,不是被调用时)就产生了。

  2. 死亡:嵌套的内部函数成为垃圾对象时。(比如fun1 = null,就可以让 fun1 成为垃圾对象)

闭包的表现形式

形式1:将一个函数作为另一个函数的返回值

    function fn1() {
      var a = 2

      function fn2() {
        a++
        console.log(a)
      }
      return fn2
    }

    var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2
    f() // 3       //执行fn2
    f() // 4       //再次执行fn2

当f()第二次执行的时候,a加1了,也就说明了:闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。

上面的代码中,虽然调用了内部函数两次,但是,闭包对象只创建了一个。

也就是说,要看闭包对象创建了几个,就看:外部函数执行了几次(与内部函数执行几次无关)。

形式2:将函数作为实参传递给另一个函数调用

在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。

    function showDelay(msg, time) {
      setTimeout(function() {  //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg
        alert(msg)
      }, time)
    }
    showDelay('qianfan', 2000)

上面的代码中,闭包是里面的function,因为它是嵌套的子函数,而且引用了外部函数的变量msg。

闭包的作用

  • 作用1:延长局部变量的生命周期。

  • 作用2:让函数外部可以操作(读写)函数内部的数据(变量/函数)。

    ​ 可以再父函数外部使用父函数的局部变量

代码演示:

function fun1() {
  let a = 2

  function fun2() {
    a++
    console.log(a)
  }
  return fun2;
}

const foo = fun1();   //执行外部函数fn1,返回的是内部函数fn2
foo() // 3       //执行fun2
foo() // 4       //再次执行fun2

上方代码中,foo 代表的就是整个 fun2 函数。当执行了 foo() 语句之后,也就执行了fun2()函数,fun1() 函数内就产生了闭包。

作用1分析

一般来说,在 fn1() 函数执行完毕后,它里面的变量 a 会立即销毁。但此时由于产生了闭包,所以 fun1 函数中的变量 a 不会立即销毁,仍然保留在内存中,因为 fn2 函数还要继续调用变量 a。只有等所有函数把变量 a 调用完了,变量 a 才会销毁。

作用2分析:

在执行 foo()语句之后,竟然能够打印出 3,这就完美通过闭包实现了:全局作用域成功访问到了局部作用域中的变量 a。

达到的效果是:外界看不到变量a,但可以操作a。当然,如果你真想看到a,可以在fun2中将a返回即可。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

案例:用闭包的方式实现mult(5)(6)(7),表示三个数的乘法(567)

function mult(m){
    return function(n){
        return function(k){
            return m*n*k;
        }
    }
}

console.log(mult(5)(6)(7));
function f1(){   //闭包的概念:函数嵌套函数,被嵌套的函数称为闭包函数
        var count = 0;//闭包的作用:可以在父函数f1外部,使用父函数f1的局部变量
                      //count
        var f2 = function(){//闭包的实现:在父函数f1中定义局部变量count,
            ++count;   //嵌套定义子函数f2,在子函数f2中操作count
                       //将f2作为f1的返回值,通过全局变量f和f1()的返回值f2对象
            return count;//进行绑定,延长了局部变量f2和count的生命周期
        }              //闭包的缺陷:延长了局部变量的生命周期,
                       //打破了垃圾回收机制,大量使用闭包会导致内存泄露
                       //delete 
        return f2;
    }
// f = f1() = f2
let f = f1();

console.log(f());
console.log(f());
console.log(f());
*/

// 变形
// function f1(){
//     var count = 0;

//     return function(){
//         return ++count;
//     }
// }
// let f = f1();

// console.log(f());
// console.log(f());
// console.log(f());

//在变形
let f = (function(){
    var count = 0;
    return function(){
        return ++count;
    }
}());

console.log(f());
console.log(f());
console.log(f());

内存溢出和内存泄露

内存泄漏

内存泄漏占用的内存没有及时释放。

内存泄露的次数积累多了,就容易导致内存溢出。

常见的内存泄露

1、意外的全局变量

2、没有及时清理的计时器或回调函数

3、闭包

情况1举例:

// 意外的全局变量
function fn() {
  a = new Array(10000000);
  console.log(a);
}

fn();

情况2举例:

// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
  console.log('----')
}, 1000)

// clearInterval(intervalId);  //清理定时器

情况3举例:

function fn1() {
  var a = 4;
  function fn2() {
    console.log(++a)
  }
  return fn2
}
var f = fn1()
f()

// f = null //让内部函数成为垃圾对象-->回收闭包

闭包面试题

代码举例:

function addCount() {
  let count = 0;
  return function () {
    count = count + 1;
    console.log(count);
  };
}

const fun1 = addCount();
const fun2 = addCount();
fun1();
fun2();

fun1();
fun2();

打印结果:

1
1
2
2

代码解释:

(1)fun1 和 fun2 这两个闭包函数是互不影响的,因此第一次调用时,count变量都是0,最终各自都输出1。

(2)第二次调用时,由于闭包有记忆性,所以各自会在上一次的结果上再加1,因此输出2。

标签:闭包,function,20,函数,console,fun1,log
From: https://www.cnblogs.com/qianfanqf/p/16969919.html

相关文章