javascript之闭包七(闭包的应用场景)

    |     2020年4月22日   |   html/css/js, web前端技术, 面试题   |     0 条评论   |    1298

闭包不只是面试名词,它会在定时器传参、事件回调、私有变量、循环绑定和 ES6 偏函数里反复出现。下面按原文五个场景把例子摊开。记住一条:返回的函数不要随手去引用循环变量。

参考:CSDN 原文;廖雪峰:闭包。

一、setTimeout 传参

原生 setTimeout 传的第一个函数不能带参数,用闭包可以做到传参:

    setTimeout(function(param){

        alert(param)

    },1000)



    //通过闭包可以实现传参效果

    function func(param){

        return function(){

            alert(param)

        }

    }

    var f1 = func(1);

    setTimeout(f1,1000);

二、回调

我们定义行为,再把它绑到点击或按键上。代码通常作为回调(事件触发时调用的函数)挂上去:

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

三、封装变量

用闭包定义能访问私有函数和私有变量的公有函数:

    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
  • 共享的环境创建在一个匿名函数体内,立即执行。
  • 环境中有一个局部变量、一个局部函数,通过匿名函数返回的对象上的三个公共函数访问。

另一个计数器

用 JavaScript 创建计数器:

'use strict';

function create_counter(initial) {

    var x = initial || 0;

    return {

        inc: function () {

            x += 1;

            return x;

        }

    }

}

运行结果:

var c1 = create_counter();

c1.inc(); // 1

c1.inc(); // 2

c1.inc(); // 3



var c2 = create_counter(10);

c2.inc(); // 11

c2.inc(); // 12

c2.inc(); // 13

返回对象里实现了一个闭包,携带局部变量 x,外部根本访问不到。闭包就是携带状态的函数,并且状态可以完全对外隐藏。

场景 闭包做了什么
setTimeout 把参数封进返回的无参函数
事件回调 changeSize(n) 返回真正的 onclick
模块/计数器 IIFE 里的私有变量只经公开方法改
循环绑定 用立即执行函数把当前 i 拷到 n
偏函数 addX 内层函数一直能读到 x

四、循环绑定的经典坑

function count() {

    var arr = [];

    for (var i=1; i<=3; i++) {

        arr.push(function () {

            return i * i;

        });

    }

    return arr;

}



var results = count();

var f1 = results[0];

var f2 = results[1];

var f3 = results[2];

每次循环都创建了一个新函数,三个函数放进数组返回。你可能以为 f1() / f2() / f3() 是 1 4 9,实际是:

f1(); // 16

f2(); // 16

f3(); // 16

全部都是 16。返回的函数引用了变量 i,但并不立刻执行。三个函数都返回时,它们引用的 i 已经变成 4,4×4=16。

返回闭包时牢记:返回函数不要引用任何循环变量,或后续会变化的变量。

如果一定要引用,再包一层,用参数绑住当前值:

function count() {

    var arr = [];

    for (var i=1; i<=3; i++) {

        arr.push((function (n) {

            return function () {

                return n * n;

            }

        })(i));

    }

    return arr;

}



var results = count();

var f1 = results[0];

var f2 = results[1];

var f3 = results[2];



f1(); // 1

f2(); // 4

f3(); // 9

这里用的是「创建匿名函数并立刻执行」:

(function (x) {

    return x * x;

})(3); // 9

五、ES6 里不经意的闭包

局部应用(partial application)就是闭包:

let c = 4

const addX = x => n => n + x

const addThree = addX(3)

let d = addThree(c)

console.log('example partial application', d)

不用箭头函数的等效写法:

let c = 4

function addX(x) {

  return function(n) {

     return n + x

  }

}

const addThree = addX(3)

let d = addThree(c)

console.log('example partial application', d)

通用加法 addX 接受 x,返回另一个函数;内层把参数和 x 相加。x 是闭包的一部分。声明 addThree 时,它带着函数定义和含 x 的闭包。调用时既能读到 x,也能读到传入的 n,控制台打印 7。

一句话总结:闭包把外层变量带进稍后执行的函数:定时器、回调、私有状态都靠它;循环里要用 IIFE/参数把当前值拷走,否则几个函数会共享同一个变了的 i。

转载请注明来源:javascript之闭包七(闭包的应用场景)
本文链接地址:https://ai.zhousir.top/?p=2909
回复 取消