JavaScript闭包

时间:2020-05-01

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

<script>
    function test1() {
        let name = 'wyzda';

        function test2() {
            return name;
        }

        console.log(test2());
    }

    test1();
</script>

通常你使用只有一个方法的对象的地方,都可以使用闭包。

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12,size14 和 size16 三个函数将分别把 body 文本调整为 12,14,16 像素。我们可以将它们分别添加到按钮的点击事件上。如下所示:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

<script>
    let Person = (function () {
        function say(msg) {
            console.log(msg)
        }

        function eat(food) {
            console.log(food)
        }

        return {
            say: say,
            eat: eat
        };
    })();

    Person.say('您好');
    Person.eat('热干面');
</script>

一个常见的错误,在循环中创建闭包

<button>btn1</button>
<button>btn2</button>
<button>btn3</button>
<script>
    var btns = document.getElementsByTagName('button');
    var len = btns.length;
    for (var i = 0; i < len; i++) {
        btns[i].onclick = function () {
            alert(i);
        };
    }
</script>

要做的功能就是点击每个按钮弹出它们当前的下标。

上面出现的一个问题是,每个按钮弹出的都是3,很显然这是错误的。

但为什么会是3了,而不是 0 ,1,2?

循环在事件触发之前早已执行完毕,闭包函数没有执行,由于变量提升,所以i具有函数作用域。

变量i(被三个闭包所共享)的值由最后2加加变成3了。

简单说就是循环执行完了,但闭包里面的代码没有执行。当点击的时候才会执行闭包函数内的代码,此时的i在循环结束时已经变成3了。

解决方式一:将闭包内的变量i与外部的变量i绑定起来

在加一个闭包函数并且立即执行,同时这个闭包函数接收变量i的值与点击事件的闭包里面的i绑定

<script>
    var btns = document.getElementsByTagName('button');
    var len = btns.length;
    for (var i = 0; i < len; i++) {
        (function (j) {
            btns[i].onclick = function () {
                alert(j);
            }
        })(i);
    }
</script>

解决方式二:避免使用过多的闭包,可以用let关键词

let允许你声明一个作用域被限制在 块级中的变量、语句或者表达式。与 var 关键字不同的是, var声明的变量只能是全局或者整个函数块的。 var 和 let 的不同之处在于后者是在编译时才初始化

<script>
    var btns = document.getElementsByTagName('button');
    var len = btns.length;
    for (let i = 0; i < len; i++) {
        btns[i].onclick = function () {
            alert(i);
        };
    }
</script>

使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。

闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次

<script>
    function Person(name) {
        this.name = name;
        // 这么写不好
        this.say = function () {
            console.log(name);
        };
    }

    // 推荐用原型来写方法
    Person.prototype.eat = function () {
        console.log('eat');
    };

    let p = new Person('wyzda');
    p.say();
    p.eat();

</script>