大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: ES进阶 修改讲义

蹊跷的问题

仔细阅读下面的代码,能不能利用changeColor()在点击button时改变<h2>的颜色?

    <h2>万紫千红</h2>
    <button style="color:red" name="change-color">变红</button>
    <button style="color:blue" name="change-color">变蓝</button>
    <button style="color:yellow" name="change-color">变黄</button>
    <script>
        function changeColor(color) {
            document.getElementsByTagName('h2')[0].style.color = color;
        }
    </script>

我们已经抽象出了函数changeColor(color),注意这个函数是带着参数的。怎么在事件的回调函数里传参呢?(复习)

@想一想@:能不能这样写?

        document.getElementsByTagName('button')[0].onclick = changeColor(this.style.color);

复习:事件绑定的应该是一个函数,而不能是函数运行结果(不能传参),怎么办呢?

  • 行内事件绑定
        <button style="color:red" onclick="changeColor(this.style.color)" name="change-color">变红</button>
    
    @想一想@:为什么这里的onclick可以直接使用()和this?(复习)可以将行内onclick绑定想象成自带了一个包裹function……
    这样需要复制粘贴n次onclick,我们还可以用循环解决问题。首先,获取所有buttons:
            var buttons = document.getElementsByTagName('button');
  • 匿名函数包裹
        for (var i = 0; i < buttons.length; i++) {
            buttons[i].onclick = function () {
                changeColor(this.style.color);//注意这里使用的是this
            }
        }
  • 函数返回函数
            buttons[i].onclick = changeColor(buttons[i].style.color);
            function changeColor(color) {
                return function () {
                    document.getElementsByTagName('h2')[0].style.color = color;
                }
            }

但是,假如你选择了这种方式:

        for (var i = 0; i < buttons.length; i++) {
            buttons[i].onclick = function () {
                document.getElementsByTagName('h2')[0].style.color = buttons[i].style.color;
            }
        }

演示:当事件触发的时候,i 的值为3

@想一想@:为什么呢?

因为事件的绑定和触发是分离的:

  • 当事件绑定时,根本就没有使用到 i
  • 当事件触发时,for循环已经执行完毕,i 值为3(注意:i 是一个“全局”变量)

怎么破?


IIFE

再显身手。代码如下:
for (var i = 0; i < buttons.length; i++) {
    //绑定时立即执行
    buttons[i].onclick = (function (n) {  //为了清晰,形参重命名为n
        //立即执行之后返回的还是函数才行
        return function () {  //n不要声明在这里,否则……
            document.getElementsByTagName('h2')[0].style.color = buttons[n].style.color;
        }
    })(i); //利用此时的i
}

信息量很大,同学们按这三步进行理解:

  1. 利用IIFE,在事件绑定时就把 i 给“拽”进来
  2. IIFE执行后必须返回一个“函数”给事件
  3. (实参)i 传递给了 (形参)n
但是,当事件触发时,函数里找不到 n 的声明了,怎么办?……


闭包

我们来看一段更简单的代码:

        function luckyStack() {
            var _price = 986;
            return function () {
                return ++_price;
            }    //函数返回了一个函数
        }
        var getPrice = luckyStack();//typeof getPrice
        alert(`"源栈"培训的价格是每周${getPrice()}元!`);
注意,我们再来捋(复习)一遍:
  1. _price未在内部匿名函数中声明
  2. 所以getPrice()被调用的时候,JavaScript解释器逐层向函数外部寻找_price的定义
  3. 此时luckyStack()已经执行完毕,按理说其中声明的_price应该已经被销毁

但是,“闭包”出现了!由于:

  • 函数返回的是一个在它内部声明的函数
  • 而且该内部函数还调用了其函数外部的变量
  • 这个链条的存在,导致JavaScript解释器无法/不会释放外部函数资源

这就被称之为:闭包

演示:每调用一次getPrice(),_price均自增一次,说明_price本身一直存在。

关于闭包的定义很多:有的非常宽泛,比如“函数作用域就是闭包”(有道理但可能没意义);有的很学究气,比如

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)

不明白这个的定义不要紧。同学们只需要理解:

  1. 闭包本质上是一个作用域问题
  2. 它的形成依赖于(词法)作用域链
  3. 其实现方式通常表现为上文所示的“函数返回函数,且内部函数使用其外部变量”
  4. 闭包的最终结果就是:延长了函数中变量的生命周期

演示:并理解上文for循环中IIFE形成的闭包



演示:将上述代码改成 let i = 986;


能完美解决上述闭包for循环的问题,让每一个匿名function都绑定了循环时的 i 变量值。参考:MDN,(演示:略)

        for (let i = 0; i < buttons.length; i++) {


作业

  • 在函数student()中声明了函数域变量name、age和female,使用闭包机制,将其暴露到函数外部
  • 解释以下代码运行结果:( condition ? <statement when true> : <statement when false>)
            function foo(x) {
                var tmp = 3;
                return function (y) {
                    x = x ? x + 1 : 1;
                    console.log(x + y + tmp);
                }
            }
    
            var bar = foo(-1);
            //或者:var bar = foo(1);
            //或者:var bar = foo(0);
    
            bar(10);
  • 改动以下代码,让其输出如图所示,并说明理由。
            function buildList(list) {
                var result = [];
                for (vari = 0; i < list.length; i++) {
                    result.push(function () {
                        console.log('item' + i + ': ' + list[i])
                    });
                }
                return result;
            }
    
            (function() {
                var fnlist = buildList([1, 2, 3]);
                for (var i = 0; i < fnlist.length; i++) {
                    fnlist[i]();
                }
            })();


学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ES进阶 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码