最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 弄懂 JavaScript闭包

    正文概述 掘金(千树同学)   2020-12-17   324

    函数的执行环境和变量的作用域

    要理解闭包,首先要理解JavaScript函数的执行环境和变量的作用域

    执行环境(Execution Context,也称为"执行上下文")是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其它数据,决定了各自的行为。当JavaScript代码执行的时候,会进入不同的执行环境,这些不同的执行环境就构成了执行环境栈。

    let name = "qianshu";
    function foo() {
      let year = 2020;
      function bar() {
        let month = 12;
        console.log(month);  //这里能读取到外部变量name、year
      }
      bar();
    }
    foo();
    

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

    let name = 'qianshu';  //全局变量
    function foo(){
        let year =2020;    //局部变量
    }
    Foo();
    console.log(year)  //ReferenceError: year is not defined. 外部函数不能读取到内部变量 
    

    什么是闭包

    在红宝书中,闭包是这样定义的。闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

    首先我们先来看一段代码。

    function outer() {
       let year =2020
    
        function insider() {
            console.log(`欢迎来到${year}年`);
        };
        return insider;
    };
    let a = outer()
    a()  //欢迎来到2020年
    

    上面这段代码inside( )函数就是一个简单的闭包函数。为了获取到内部数据(变量/函数),inside( )函数作为一个返回值,让outer( )函数读取到它内部变量。

    那么,我的理解是,闭包就是能够读取其他函数内部变量的函数。A( insider )函数嵌套在B( outer )函数内,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包。

    要使用闭包,只需要简单地将一个函数定义在另一个函数内部,并将它暴露出来。要暴露一个函数,可以将它返回或者传给其他函数。

    内部函数将能够访问到外部函数作用域中的变量,即使外部函数已经执行完毕。

    闭包的作用

    闭包的作用

    • 使用函数内部的变量在函数执行完后,仍然存活在内存中,延伸了函数环境生命周期

      ​ 父级变量被子函数调用,则父级作用域中的变量都会被保存在内存中

    • 让函数外部可以操作到函数内部的数据

    //延伸函数环境的生命周期例子
    function foo() {
      let n = 1;
      return function sum() {
        console.log(++n);
      };
    }
    let a = foo();
    a();  // 2
    a();  //3
    
    //没有闭包的例子
    function hd() {
      let n = 1;
      return function sum() {
        console.log(++n);
      };
    }
    hd()();  //2
    hd()(); //2
    
    //让函数外部可以操作到函数内部的数据的例子 
     function f1(){
    
        var n=999;
    
        function f2(){
          alert(n);
        }
    
        return f2;
    
      }
    
      var result=f1();
    
      result(); // 999
    

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

    闭包常见应用场景

    获取区间数值

    let arr = [1, 2, 3, 15, 54, 31, 65, 10];
    function between(a, b) {
      return function (v) {
        return v >= a && v <= b;
      };
    }
    let data = arr.filter(between(2, 20));
    console.log(data);  //[2,3,15,10]
    
    

    防抖

    事件相应函数在一段时间后才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定时间内没有再次调用,则执行doSomeThing。(现实生活中的例子:公交车司机开门让乘客上车,要等所有乘客都上车后才关门,而不是每上一位乘客执行一次开关门的操作)

    function debounce(func, wait, immedicate) {
      let timeoutm,result;
      return function () {
        //改变执行函数内部的this的指向
        let context = this;
        //event 指向问题
        let args = arguments;
        if (timeout) clearTimeout(timeout);
        if (immedicate) {
          let callNow = !timeout;
          timeout = setTimeout(() => {
            timeout = null;
          }, wait);
          //立即执行
          if (callNow) result = func.apply(context, args);
        } else {
          timeout = setTimeout(function () {
            func.apply(context, args);
          }, wait);
        }
        return result;
      };
    }
    //测试代码
    let count = 0;
    let container = document.querySelector("#container");
    function doSomeThing(e) {
      container.innerHTML = count++;
    }
    container.onmousemove = debounce(doSomeThing, 300, true);
    

    节流

    节流,就是指连续触发事件但是在 n 秒中只执行一次函数。

    function throttle(func, wait) {
      let context, args, timeout;
      let old = 0; //时间戳
      let later = function () {
        old = new Date().valueOf();
        timeout = null;
        func.apply(context, args);
      };
      return function () {
        context = this;
        args = arguments;
        //获取当前时间戳
        let now = new Date().valueOf();
        if (now - old > wait) {
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          func.apply(context, args);
          old = now;
        } else if (!timeout) {
          timeout = setTimeout(later, wait);
        }
      };
    }
    //测试代码
    let count = 0;
    let container = document.querySelector("#container");
    function doSomeThing(e) {
      container.innerHTML = count++;
    }
    container.onmousemove = throttle(doSomeThing, 300, true);
    

    使用闭包的注意点

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

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

    <body>
       <div desc="foo">123</div>
        <div desc="bar">345</div> 
    </body>
    <script>
    //内存泄漏
    let divs = document.querySelectorAll("div");
    divs.forEach(function (item) {
      let desc = item.getAttribute("desc");
      item.addEventListener("click", function () {
        console.log(desc);
        console.log(item);
      });
      item = null;   //退出函数之前,将不使用的局部变量删除
    });
    </script>
    

    参考链接

    • 学习JavaScript闭包(Closure),阮一峰

    • 闭包与作用域,后盾人教程


    起源地下载网 » 弄懂 JavaScript闭包

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元