【JS】用小案例体现闭包的应用,let与块级作用域,如何保存变量

123 阅读5分钟

案例

写一个简单的给背景换色的案例来体现如何保存变量。

需求效果:有三个按钮,点击一个按钮,页面背景变为对应的颜色

使用两种方法来实现效果:

1、自定义属性的方式:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        button {
            width: 300px;
            height: 200px;
        }
    </style>
</head>

<body>
    <button>红色</button>
    <button>黄色</button>
    <button>蓝色</button>
</body>
<script>
    var ary = ["red", "yellow", "blue"];
    var buttons = document.getElementsByTagName("button");
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].myColor = ary[i];
        buttons[i].onclick = function () {
            document.body.style.backgroundColor = this.myColor;
        }
    }
</script>

</html>

2、闭包的方式:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        button {
            width: 300px;
            height: 200px;
        }
    </style>
</head>

<body>
    <button>红色</button>
    <button>黄色</button>
    <button>蓝色</button>
</body>
<script>
    var ary = ["red", "yellow", "blue"];
    var buttons = document.getElementsByTagName("button");
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].onclick = (function (i) {
            return function () {
                alert(ary[i]);
                document.body.style.backgroundColor = ary[i];
            }
        })(i)
    }
</script>

</html>

注意: 如果只是简单一个for循环,不加入任何可以保存变量的措施,最终for循环结束以后,全局变量 i 变为了3,导致无法实现对应的效果。

保存变量

之前提到过,目前学到的保存变量的措施有两种:一种是自定义属性的方式,一种是使用闭包的机制。

  • 自定义属性:给元素对象设置自定义属性,让需要被保存的值保存在自定义属性中,相当于把这个值挂载到了对象上,当需要使用的时候,就使用对象成员访问即可调用这个值。
  • 闭包:上面案例中,自执行函数里面return 返回了一个引用类型function,并且这个函数被外界的元素对象所接收,所以自执行函数执行形成的私有作用域不销毁,里面的私有变量 i 就被保存了下来。由于本案例中for循环执行了三次,相应的也就有三个未销毁的作用域,每个作用域中的私有变量i都被保存了下来。当外界button这个元素对象被点击时,触发其绑定的函数,函数中用到的变量不是私有变量则会去上级作用域查找,找到的是对应的自执行函数的未销毁的作用域中的私有变量i,也就可以实现案例的效果。

注: 这两种方法对比起来,闭包形成了三个不销毁的私有作用域,太消耗性能,而自定义属性的方式更节约性能,建议使用自定义属性的方式。

除了这两种保存变量的方法以外,还有一个使用ES6的方法来实现保存变量的目的。

let与块级作用域

ES6之前的作用域只有两种:

1、全局的

2、函数里面私有

有了ES6以后,就有一个块级作用域的概念。 当然,前提是使用ES6的语法,除了函数和对象的大括号,当遇到了 {} 就会形成一个块级作用域。

let 是ES6 的语法,当大括号中有 let 声明变量时(除了函数和对象的大括号),就会形成块级作用域了,比如遇到for循环的大括号,就会形成块级作用域。

因此上面的案例还可以这样写:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        button {
            width: 300px;
            height: 200px;
        }
    </style>
</head>

<body>
    <button>红色</button>
    <button>黄色</button>
    <button>蓝色</button>
</body>
<script>
    var ary = ["red", "yellow", "blue"];
    var buttons = document.getElementsByTagName("button");
    for (let i = 0; i < buttons.length; i++) {
        buttons[i].onclick = function () {
            alert(ary[i]);
            document.body.style.backgroundColor = ary[i];
        }
    }
</script>

</html>

let 遇到 for 循环的大括号会形成块级作用域,块级作用域的作用与闭包类似,也可以保护和保存变量。for循环执行了三次代码,也就形成了三个块级作用域,当元素对象被点击时,也是可以通过查找机制,去块级作用域查找相应的变量i,也就实现了案例的效果。

事件委托实现这个案例(性能优化)

前面几种方法都比较消耗性能,相比较而言自定义属性相对比较节约性能一点,但也不是最优方案,还可以进行优化,那么使用节约性能的方法来实现这个效果该如何实现呢?

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        button {
            width: 300px;
            height: 200px;
        }
    </style>
</head>

<body>
    <button index="0">红色</button>
    <button index="1">黄色</button>
    <button index="2">蓝色</button>
</body>
<script>
    var ary = ["red", "yellow", "blue"];
    var buttons = document.getElementsByTagName("button");

    document.body.onclick = function (ev) {
        var target = ev.target;
        // console.log(target);
        if (target.tagName == "BUTTON") {
            // console.log(target.getAttribute("index"));
            var index = target.getAttribute("index");
            document.body.style.backgroundColor = ary[index];
        }
    }
</script>

</html>

事件委托的方法,就是给元素的父级元素绑定事件,则当在其中的子代元素触发相应的事件时,也会触发父级元素绑定的事件,执行绑定的函数。