zoukankan      html  css  js  c++  java
  • 折腾自己的js闭包(二)

    前面我大致探讨了js里的闭包的相关概念,那么,到底在什么时候用它最好呢?存在即真理,只不过以前没发现它而已,先来看看下面的这几个用途吧

    一、我首先想到的就是从函数外面访问它的内部变量,从而达到自己的一些目的,还避免了设定为全局变量的全局变量污染,例如如下这个封装:

    var person = function(){

    //变量作用域为函数内部,外部无法访问

      var name = "default name";

      return {

      getName : function(){

        return name;

        },

      setName : function(newName){

        name = newName;

      }

      }

    }();

    console.log(person.name); //结果为undefined

    console.log(person.getName()); //结果为default name

    person.setName("xiangxiao");

    console.log(person.getName()); //结果为xiangxiao

    但是后来想想觉得不大对劲,为何不直接用新建对象的方式来完成呢?或者angular严格的作用域控制其实也可以。要使用的时候就加上 object.变量名/object.方法名();或者在用到angular时的$scope.变量名/$scope.方法名()。猛的反应过来,局部变量,如果用了上述的那些解决办法,那还能叫函数的内部变量吗?不过这种封装的方式在我目前见过的项目里还很少用到。

    二、查阅了些资料,发现了它的第二个用途,就是模拟面向对象的基类,对啊,JavaScript虽然没提供明显的类继承,但是仍然有另外的路径来实现同java一样 的继承呀,比如说prototype原型就是一个解决方案,严格的说prototype并不是继承,而是对函数prototype属性的一种拷贝来实现的类似于继承的东西。现在先来看看闭包是怎么模拟的吧,例如:

    function Empolyee(){

      var station = "default";  

      return {

      getStation : function(){

        return station ;

        },

      setStation : function(newStation){

        station = newStation;

      }

    };

    var xiaoming = Empolyee();

    console.log(xiaoming.station); //结果为default

    xiaoming.setStation("C++ coder");

    console.log(xiaoming.getStation()); //结果为C++ coder

    var xiaozhang = Empolyee();

    console.log(xiaozhang.station); //结果为default

    xiaozhang.setStation("java coder");

    console.log(xiaozhang.getStation());//结果为java coder

    其实仔细一看感觉模拟面向对象这个说法有些牵强,但确实实现了xiaoming和xiaozhang这两个雇员都是来自方法Empolyee的,而且互不影响,xiaoming的岗位是C++程序员,xiaozhang的gangwei是java程序员,相互独立。这点和函数原型prototype超级接近,所以说啊,我们的JavaScript还是包罗万象的。

    此处还真的必须用闭包来实现,不写这个return一个对象的话,两个实体的还真没有共同方法setStation和getStation。

    三、某些只需执行一次的函数,也即是常说的自执行匿名函数,这个在我最近参与的项目里好几处都有用到,基本都是在一个界面的初始化中,初始化需要立马下发一次ajax请求,查上来的数据立马要反馈在页面上显示,而且只需执行这一次即可,内部变量无需再次利用,这时候,用到自执行匿名函数就最合适不过了。例如:

    var datamodel = {

      table : [],

      tree : {}

    };

    (function(dm){

      for(var i = 0; i < dm.table.rows; i++){

        var row = dm.table.rows[i];

        for(var j = 0; j < row.cells; i++){

          drawCell(i, j);

        }

      };

    })(datamodel);

    这就是一个初始化渲染树结构的自执行匿名函数,我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

    记得以前初识闭包时,遇见过一个的闭包的例子,是这样的:

    function cal(num1){
      var num2 = 3;
      function innerCal1(num2){
        var num3 = 3;
        function innerCal2(num3){
          return num3*num2*num1;
        }
        return innerCal2;
      };
      return innerCal1;
    };

    cal(3)(3)(3); //结果为27

    其实这个结果倒不意外,但是这么写很让人费解,当时一看特别懵,当了解了闭包的这一系列知识之后,再来看这例子就轻松些了,这算是一个闭包嵌套,内部返回函数innerCal2,外部再返回一个函数innerCal1,两次叠加,相当于可执行2次的闭包,再依次传入实参,就可以进行计算了。

    另外,还有一个经常被各公司面试官用来考人的面试题(其实是以前有2个同事出去应聘时都被问到了),看来不可小觑,比较经典,是这样的:

    <!DOCTYPE html>

    <html lang="en">

    <head> <meta charset="UTF-8">

    <title></title>

    <script type="text/javascript">

      function onMyLoad(){

       var arr = document.getElementsByTagName("p");

       for(var i = 0; i < arr.length;i++){

         arr[i].onclick = function(){

           alert(i);

         }

       };

    };

    </script>

    </head>

    <body onload="onMyLoad()">

       <p>产品一</p>

       <p>产品二</p>

       <p>产品三</p>

       <p>产品四</p>

       <p>产品五</p>

    </body>

    </html>

    抛出问题: 此题的目的是想每次点击对应目标时弹出对应的数字下标 0~4,但实际是无论点击哪个目标都会弹出数字5 问题所在: arr 中的每一项的 onclick 均为一个函数实例(Function 对象),这个函数实例也产生了一个闭包域, 这个闭包域引用了外部闭包域的变量,其 function scope 的 closure 对象有个名为 i 的引用, 外部闭包域的私有变量内容发生变化,内部闭包域得到的值自然会发生改变 。

    这么一大段话,解释得够清楚的了,也即是在整个for循环之后,i的值已经变成4了,这时候点击执行的onclick函数,当然内部变量i也随之变成4了,我们想要的效果并不能实现,针对这个问题,也可用闭包来解决,如下:

    function onMyLoad(){

       var arr = document.getElementsByTagName("p");

       for(var i = 0; i < arr.length;i++){

         (function(arg){

           arr[i].onclick = function(){

           alert(i);

           };

        })(i);   

       };

    };

    这么做的思路是:既然onclick函数里的i是随着for循环时 i变化而跟着变化的,那我们干脆不用这个i,用一个跟它保持关联的变量来代替它,不就行了?当然,这么做的前提是基本变量类型,要知道,对象或者其他引用类型是不会这么听话的,一边变化,保存它的内存那儿也跟着变化,立马跟它相等的变量一股脑儿全变了。现在来看看这么做的原理是什么。

    此处定义了一个闭包函数,并立即执行它,该函数有一个私有的变量arg,该函数的function scope的闭包对象有2个引用,arg和i,虽然i随着for循环是不断在变化着的,但是函数的形参arg是不会立即跟i保持变化,毕竟不是引用类型的变量,内存保存的是值,并不是一个指针,因此这里arg并不会立马随着i的变化而变化。同时,i是这个匿名函数的实参,被传入匿名函数中,因此,i等于多少,里面arg也就等于多少。这样子,我们的目的就达到了。运行一下,果真如此,弹出的值依次是0~4。

    网上还有一个超赞的解决办法,将i变为这个arr数组中每个元素的某个属性,其实这种方法也是基于变量值转移的思路,不直接使用变量i,使用另一个跟i值相等的变量来代替它,好了,来看看这样子又是怎么实现的吧,如下:

    function onMyLoad() {

      var arr = document.getElementsByTagName("p");

      for(var i = 0; i<arr.length;i++){

        arr[i].number = i; //注意此处,将i的值赋给数组arr中每个元素的属性number,这样就实现了变量转移。
        arr[i].onclick = function(){

          alert(this.number);
        }
      }

    };

    运行一下,果真也是如此,弹出的值依次是0~4。

    关于闭包一些使用,暂时就告一段落了,最近在追一部红遍大江南北的国产剧《人民的名义》,不多说了,去看看侯局长怎么略施小计收拾那帮拿钱不干人事的贪官的,达康书记的表情包也跟着网红了,哈哈哈。

  • 相关阅读:
    Java实现 蓝桥杯 算法提高 特等奖学金(暴力)
    Java实现 蓝桥杯 算法提高 特等奖学金(暴力)
    Java实现 蓝桥杯 算法提高 GPA(暴力)
    Java实现 蓝桥杯 算法提高 GPA(暴力)
    Java实现 蓝桥杯 算法提高 GPA(暴力)
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    第一届云原生应用大赛火热报名中! helm install “一键安装”应用触手可及!
    云原生时代,2个方案轻松加速百万级镜像
    Knative 基本功能深入剖析:Knative Serving 自动扩缩容 Autoscaler
  • 原文地址:https://www.cnblogs.com/xiangxiao/p/6675940.html
Copyright © 2011-2022 走看看