Bootstrap

闭包概念以及场景举例

概念

闭包是来解决什么问题的?

js中,函数无法访问其他函数里面的变量,而有些场景需要我们去访问其他函数里面的变量,那你就要使用闭包了。

常见例子

	functtion foo() {
		var name = 'demo'
		var getName = function() {
			return name
		}
		return getName // 也可以是个对象装满各种操作函数
	}
	let action = foo()
	let fooName = action()

上面就是个典型的闭包例子,foo函数实例化后,返回getName函数,赋值给window.action,action一直存在内存中,直到关闭页面。
由于action依赖foo函数提供的动态上下文环境,所以垃圾回收机制不会回收foo。
当我们不需要这个闭包的时候,我们知道它并不会自动消失,所以如果一直调用类似的闭包,就可能出现内存泄漏。
所以我们需要在不使用闭包的时候,将其设置为null

action = null

例子

1、data使用函数返回值

...
	data() {
		return {
			list:[]
		}
	}
...

2、闭包应用场景之setTimeout

    //原生的setTimeout传递的第一个函数不能带参数
    setTimeout(function(param){
        alert(param)
    },1000)


    //通过闭包可以实现传参效果
    function func(param){
        return function(){
            alert(param)
        }
    }
    var f1 = func(1);
    setTimeout(f1,1000);

3、闭包应用场景之回调

<!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>

4、闭包应用场景之封装变量

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>闭包模拟私有方法</title>
    <link rel="stylesheet" href="">
</head>
<body>
<script>
    //用闭包定义能访问私有函数和私有变量的公有函数。
    var counter = (function(){
        var privateCounter = 0; //私有变量
        function change(val){
            privateCounter += val;
        }
        return {
            increment:function(){   //三个闭包共享一个词法环境
                change(1);
            },
            decrement:function(){
                change(-1);
            },
            value:function(){
                return privateCounter;
            }
        };
    })();

    console.log(counter.value());//0
    counter.increment();
    counter.increment();//2
    //共享的环境创建在一个匿名函数体内,立即执行。
    //环境中有一个局部变量一个局部函数,通过匿名函数返回的对象的三个公共函数访问。

</script>
</body>
</html>

5、闭包应用场景之为节点循环绑定click事件

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="">
</head>
<body>

    <p id="info">123</p>
    <p>E-mail: <input type="text" id="email" name="email"></p>
    <p>Name: <input type="text" id="name" name="name"></p>
    <p>Age: <input type="text" id="age" name="age"></p>

<script>
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };

    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            var item = infoArr[i];
            document.getElementById(item.id).onfocus = function(){
                showContent(item.content)
            }
        }
    }
    setContent()
    //循环中创建了三个闭包,他们使用了相同的词法环境item,item.content是变化的变量
    //当onfocus执行时,item.content才确定,此时循环已经结束,三个闭包共享的item已经指向数组最后一项。



    /**
     * 解决方法1     通过函数工厂,则函数为每一个回调都创建一个新的词法环境
     */
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };

    function callBack(content){
        return function(){
            showContent(content)
        }
    };

    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            var item = infoArr[i];
            document.getElementById(item.id).onfocus = callBack(item.content)
        }
    }
    setContent()

    /**
     * 解决方法2        绑定事件放在立即执行函数中
     */
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };

    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            (function(){
                var item = infoArr[i];
                document.getElementById(item.id).onfocus = function(){
                    showContent(item.content)
                }
            })()//放立即执行函数,立即绑定,用每次的值绑定到事件上,而不是循环结束的值
        }
    }
    setContent()

    /**
     * 解决方案3        用ES6声明,避免声明提前,作用域只在当前块内
     */
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };

    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            let item = infoArr[i];      //限制作用域只在当前块内
            document.getElementById(item.id).onfocus = function(){
                showContent(item.content)
            }
        }
    }
    setContent()
</script>
</body>
</html>

6,遍历器构造器

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
// 闭包
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}
  1. 缓存数据 cache的实现
  2. 柯里化 currying的相关实现(全部柯里化、部分柯里化)
  3. 设计模式
  4. 函数式编程

上面一些概念里面的应用是互相渗透的。

总结:上面的场景使用都会形成闭包,那么随之而来的就是内存占用和内存泄露问题了,当关闭页面时(例如在vue单页面应用下),页面是关闭了,但是内存实际还是占用着,这时候开的页面多了,内存就会越来越来,当小项目多了,而里面的复用也多了,互相搬迁代码,就会形成内存泄露现象,造成浏览器内存报警或者卡死。所以我们需要去查找之前的代码里面是否存在内存泄露以及在页面销毁时做好闭包销毁工作。

;