教程 前端-从零开始系列-18:JS 闭包与作用域

zoeDylan · 2017年11月02日 · 最后由 dsdr7v 回复于 2017年11月17日 · 448 次阅读

前端-从零开始系列-18:JS闭包与作用域

闭包和作用域是JS两大特点,这两点会了,JS也算学完一半了。

我这里简单讲解一下作为了解,需要深入还是后期运用中慢慢接触。

作用域

1. 什么是作用域

简单理解就是一个函数,在代码块中可访问,那么这个代码块就是这个函数的一个作用域。

<script>
    var a = 123;
    function showA(){
        document.writeln('showA:',a);
    }
    showA();
    document.writeln('global:',a);
</script>

上面代码中,showA方法和外面都可以访问a函数。

函数所定义代码块和它所在子块包括子块的子块就是这个函数的作用域。

在一个代码块中,它可以访问它本身和它之外的函数。

<script>
    var a = 123;
    function showA() {
        var b = '输出:';
        document.writeln(b + a);
    }
    showA();
    document.writeln('当前代码块:', a);
    try {
        document.writeln('子块:', b);
    } catch (err) {
        document.writeln('子块:', err);
    }
</script>

上面代码中,在最外层代码块访问b函数,但是b是在showA里面定义的,所以最外层代码库并没有b函数,就出现了报错。

2. 后来居上,向上查找

抛去函数定义上下文,在使用一个函数的时候,这个函数值遵循一个规则:后来居上,向上查找。

后来居上,意思是:函数定义后,越是在距使用最近定义的值,在使用的时候就是这个值。

向上查找:在当前代码块没有定义这个函数,调用时会使用父块的函数。

<script>
    function showA() {
        document.writeln(a);
    }
    var a = 123;
    document.writeln(a);
    a = 321;
    document.writeln(a);
    a = 333;
    showA();
</script>

function调用时才会对内部代码进行执行。

3. 全局变量

在浏览器最顶层定义的函数就是全局变量,浏览器所有全局变量都在window函数下面。

<script>
       var a = 123;
       document.writeln(window.a);
   </script>

在非严格模式下(use strict),在任何代码块中直接定义函数都会成为全局变量.


<script>
    function setA() {
        a = 123;
    }

    try {
        document.writeln(a);
    } catch (err) {
        document.writeln(err);
    }
    setA();
    document.writeln(a);
</script>

引导:Javascript 严格模式详解

4. 局部变量

局部变量就是在当前代码库和子块下可以访问的函数。

<script>
       function setA() {
           var a = 123;
       }

       try {
           document.writeln(a);
       } catch (err) {
           document.writeln(err);
       }
       setA(); 
       try {
           document.writeln(a);
       } catch (err) {
           document.writeln(err);
       }
   </script>

上面代码中,在setA中的a函数就是setA的一个局部变量,在setA代码块之外是无法访问的。

闭包

1. 什么是闭包

简单理解:闭包可以读取其它函数的内部函数。

<script>
    function setA() {
        var a = 123;
        return function() {
            document.writeln(a);
        }
    }
    var showA = setA();
    showA();
</script>

上面的代码中,showAsetA的一个返回值,但是showA所处的代码块在setA代码块的外面,按照作用域的概念是不应该访问到a函数,不过因为showA执行的方法来源于setA内部,所以showA可以访问到a函数,这就是一个简单的闭包实现。

2. 闭包的使用

闭包有三个优势:

  1. 可以操作函数内部变量
  2. 可以变量一直存放在内存中。
  3. 不污染全局变量的变量
<script>
    function setA() {
        var a = 123;
        return function() {
            document.writeln(a);
        }
    }
    var showA = setA();
    showA();

    function setA2(){
        var a = 300;
        showA();
    }
    setA2();
</script>

上面代码中,无论我们什么时候调用定义后的showA,他所使用的值始终是123,但是全局变量也没有a函数。

运用本篇讲点写一个小应用

<style>
       table {
           width: 300px;
           text-align: center;
       }
   </style>
   <table>
       <thead>
           <tr>
               <th>姓名</th>
               <th>年龄</th>
               <th>性别</th>
           </tr>
       </thead>
       <tbody>

       </tbody>
   </table>
   <p> 姓名:<input type="text" class="name"></p>
   <p> 年龄:<input type="text" class="age"></p>
   <p> 性别:<label><input type="radio" checked="checked" name="sex" value="男"></label>
       <label><input type="radio" name="sex" value="女"></label></p>
   <p>
       <button class="add">添加</button>
       <button class="man">查看男生</button>
       <button class="woman">查看女生</button>
       <button class="all">查看全部</button>
       <button class="clear">清空</button>
   </p>


   <script>
       //闭包、作用域运用
       //用户数据
       var userData = function() {
           var list = [];
           //设置用户
           function set(user) {
               list.push(user);
           }
           //获取全部用户
           function getAll() {
               return list;
           }
           //获取指定条件用户:男、女,默认男
           function getMan(man) {
               man = man ? man : '男'; //三元运算
               var tmp = [];
               for (var i = 0; i < list.length; i++) {
                   var now = list[i];
                   if (now.sex === man) {
                       tmp.push(now);
                   }
               }
               return tmp;
           }

           return {
               set: set,
               getAll: getAll,
               getMan: getMan
           }
       }();


       //dom事件、创建修改等运用
       var
       //赋予一个变量名,减少代码量:bind函数这里知道就可以了
           $ = document.querySelector.bind(document),
           _tbody = $('tbody'),
           _name = $('.name'),
           _age = $('.age'),
           btn_add = $('.add'),
           btn_man = $('.man'),
           btn_woman = $('.woman'),
           btn_all = $('.all'),
           btn_clear = $('.clear');

       //把用户展示到页面上面
       function addUser(user) {
           //判断是否是数组,如果是数组就进行递归
           if (Array.isArray(user)) {
               //递归算法
               for (var i = 0; i < user.length; i++) {
                   addUser(user[i]);
               }
           } else {
               var tr = document.createElement('tr');
               tr.innerHTML = '<td>' + user.name + '</td><td>' + user.age + '</td><td>' + user.sex + '</td>';
               _tbody.appendChild(tr);
           }
       }
       //清空展示用户
       function clearUser() {
           _tbody.innerHTML = '';
       }
       //添加用户
       btn_add.addEventListener('click', function() {
           var sex = $('input:checked'); //因为性别是单选,所以要动态获取 
           if (_name.value.length <= 0) {
               alert('姓名不能为空');
           } else if (_age.value.length <= 0) {
               alert('年龄不能为空');
           } else {
               var user = {
                   name: _name.value,
                   age: _age.value,
                   sex: sex.value
               };
               userData.set(user);
               addUser(user);
               _name.value = '';
               _age.value = '';
           }
       });
       //查看男生
       btn_man.addEventListener('click', function() {
           clearUser();
           addUser(userData.getMan());
       });
       //查看女生
       btn_woman.addEventListener('click', function() {
           clearUser();
           addUser(userData.getMan('女'));
       });
       //查看全部
       btn_all.addEventListener('click', function() {
           clearUser();
           addUser(userData.getAll());
       });
       btn_clear.addEventListener('click', function() {
           clearUser();
       });
       //添加点数据
       userData.set({
           name: '小黑',
           age: '12',
           sex: '男'
       });
       userData.set({
           name: '小红',
           age: '13',
           sex: '女'
       });
       userData.set({
           name: '小白',
           age: '15',
           sex: '男'
       });
       userData.set({
           name: '大黄',
           age: '11',
           sex: '女'
       });
   </script>
共收到 4 条回复

闭包可以读取其它函数的内部函数。 这句话有问题,其它函数应该替换成父函数

zeimeiaj 回复

你说的也没错,不过这个理解方式吧,我看其它的解释都有不同

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册