JavaScript-闭包

x33g5p2x  于2022-04-17 转载在 Java  
字(4.8k)|赞(0)|评价(0)|浏览(253)

闭包介绍

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
要理解闭包,首先必须理解Javascript特殊的变量作用域。

1.全局变量和局部变量。

2.js中当前作用域能访问其上层作用域的变量和函数

JS中当遇到对变量名或者函数名的使用时,会首先在当前作用域查找变量或者函数,如果没有找到,就会到其上层作用域中寻找,并以此类推。

 function f1(){
    var n=999;
  }

  alert(n); // error

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现,那就是在函数的内部,再定义一个函数。

 function f1(){

    var n=999;

    function f2(){
      alert(n); // 999
    }

  }

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

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

 function f1(){
    var n=999;
    function f2(){
      alert(n); 
    }
    return f2;
  }
  var result=f1();
  result(); // 999

以上就是闭包的核心概念

闭包概念

闭包特点:

  1. 函数嵌套函数
  2. 内部函数可以访问外部函数的变量
  3. 参数和变量不会被回收

各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。怎么来理解这句话呢?请看下面的代码。

function f1() {

            var n = 999;
            //在函数内部没有使用修饰符修饰的变量,自动升级为全局变量
            nAdd = function () {
                n += 1
            }

            function f2() {
                alert(n);
            }

            return f2;

        }

        var result = f1();

        result(); // 999

        nAdd();

        result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var let 等关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

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

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

闭包的优缺点

闭包的优点
可以可在全局重复使用变量,并且不会造成变量污染

全局变量可以重复使用,但是容易造成变量污染。局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。闭包结合了全局变量和局部变量的优点。
可以用来定义私有属性和私有方法。

闭包的缺点
比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。
什么是内存泄漏
首先,需要了解浏览器自身的内存回收机制。
每个浏览器会有自己的一套回收机制,当分配出去的内存不使用的时候便会回收;内存泄露的根本原因就是你的代码中分配了一些‘顽固的’内存,浏览器无法进行回收,如果这些’顽固的’内存还在一直不停地分配就会导致后面所用内存不足,造成泄露。

闭包造成内存泄漏
因为闭包就是能够访问外部函数变量的一个函数,而函数是必须保存在内存中的对象,所以位于函数执行上下文中的所有变量也需要保存在内存中,这样就不会被回收,如果一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏

闭包的使用场景案例

函数防抖

/**
         * 就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
         * 通俗一点:在一段固定的时间内,只能触发一次函数,在多次触发事件时,只执行最后一次。
         * @function debounce 函数防抖
         * @param {Function} fn 需要防抖的函数
         * @param {Number} interval 间隔时间
         * @return {Function} 经过防抖处理的函数
         * */
        function debounce(fn, interval = 300) {
            let timer = null; // 定时器
            return function () {
                // 清除上一次的定时器
                clearTimeout(timer);
                // 拿到当前的函数作用域
                let _this = this;
                // 拿到当前函数的参数数组
                let args = Array.prototype.slice.call(arguments, 0);
                // 开启倒计时定时器
                timer = setTimeout(function () {
                    // 通过apply传递当前函数this,以及参数
                    fn.apply(_this, args);
                    // 默认300ms执行
                }, interval)
            }
        }

使用

<button onclick="testf()">函数防抖点击</button>

        function test() {
            console.log("========");

        }

        let testf = debounce(test)

函数限流

/**
         * 就是限制一个函数在一定时间内只能执行一次。
         * @function throttle 函数节流
         * @param {Function} fn 需要节流的函数
         * @param {Number} interval 间隔时间
         * @return {Function} 经过节流处理的函数
         * */
        function throttle(fn, interval = 500) {
            let timer = null; // 定时器
            let firstTime = true; // 判断是否是第一次执行
            // 利用闭包
            return function () {
                // 拿到函数的参数数组
                let args = Array.prototype.slice.call(arguments, 0);
                // 拿到当前的函数作用域
                let _this = this;
                // 如果是第一次执行的话,需要立即执行该函数
                if (firstTime) {
                    // 通过apply,绑定当前函数的作用域以及传递参数
                    fn.apply(_this, args);
                    // 修改标识为null,释放内存
                    firstTime = null;
                }
                // 如果当前有正在等待执行的函数则直接返回
                if (timer) return;
                // 开启一个倒计时定时器
                timer = setTimeout(function () {
                    // 通过apply,绑定当前函数的作用域以及传递参数
                    fn.apply(_this, args);
                    // 清除之前的定时器
                    timer = null;
                    // 默认500ms执行一次
                }, interval)
            }
        }

使用

<button onclick="testT()">函数限流</button>
 function test() {
   console.log("========");

 }

 let testT = throttle(test)

使用闭包方式完成递归

arguments 的主要用途是保存函数参数,这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数,这有利于匿名函数的递归或者保证函数的封装性。。

function show(n) {
    var arr = [];
    return (function () {
        arr.unshift(n);
        n--;
        if (n != 0) {
        // 在次调用当前的匿名函数
            arguments.callee();
        }
        return arr;
    })()
}
show(5)//[1,2,3,4,5]

实现变量共享

学过java的同学相信下面代码看着并不陌生,简单来说就是将方法内的变量能提供给外部访问和修改

var person=function (){
//变量作用域为函数内部,外部无法访问
var name = 'ruirui.xu';
return {
 getName:function(){
   return name; 
   }
 setName :function(){
     name = newName;
    }
  }
}()

//在person之外的地方无法访问其内部变量,而通过提供闭包的形式访问。

alert(person.name)  //直接访问,结果undefined

alert(person.getName())    //ruirui.xu

person.setName('xuruirui')

alert('person.getName())//xuruirui

实现闭包继承

function Person(){    
    var name = "default";      
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
    };   
    var p = new Person();
    p.setName("Tom");
    alert(p.getName());
    //创建一个新的方法
    var Jack = function () {
            alert("Jack")
    };
    //继承自Person
    Jack.prototype = new Person();
    //添加私有方法
    Jack.prototype.Say = function(){
        alert("Hello,my name is Jack");
    };
    var j = new Jack();
    j.setName("Jack");
    j.Say();
    alert(j.getName());

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章