• 函数表达式 及 闭包


    函数表达式
      函数又两种定义方式:
        ①.函数声明
          它有个特性叫 函数声明提升.可以把这个声明放到最后面,前面调用不会报错.
            var ret = abc();
            function abc(){return "abc";}
        ②.就是本页重点说的 函数表达式
          它不具有所谓的"提升",所以一定先定义好,再在下面调用才行.
          函数表达式有几种方式: 赋给变量 , 作为返回值 , 作为函数参数
     
          创建了一个匿名函数(也叫lambda函数,拉姆达)
     
            var func = function(){} //赋给变量.
     
            function outer(){
              return function( arg ){ // 作为返回值
                console.log(arg);
              }
            }
     
          以下写法时极其不好的 :
            if(condition){function func_a(){}}
            else{function func_b(){}}
          但可以用函数表达式改写
            var func ;
            if(condition){func = function(){}}
            else{func = function(){}}
     
     
    函数表达式 在递归中的写法
      经典阶乘函数 提到过一种 使用 arguments.callee 的用法,用于把实现和函数名称解耦.
      但是在严格模式下,会报错.因此可以用下面的形式来折衷处理.
     
      var func = (function factorial(num){
        if(num <= 1) return 1 ;
        else return num * factorial(num -1);
      });
     
      func(3); //display 6
      var another = func ;
      another(2); //display 2
     
      这样即使有其他变量接收这个函数(another),也不会导致another()执行时,找不到factorial的情况.
     
     
    闭包
      所谓闭包,就是在函数中创建了一个函数.这个内部的函数,拥有者访问外部函数所有变量的权利.
      关于函数/作用域,有个活动对象的概念.即当前作用域中所有的数据(无论是变量还是方法),都会挂载到这个活动对象上.
      比如:
        function outer(age){ 
          var result = "result";
     
          return function(index){ 
            var inner_var = "variable";
             // do something
          }
        }
      这里内部函数的活跃对象 可以看做是
        {
          index: undefined,
          inner_var: "variable",
          arguments: [{index:xxxx}]
        }
      外部函数活动对象 , 可以理解为:
        {
          arguments:[{age:xxxx}],
          age:xxx,  
          result : "result"
        }
      那它们还有个 全局变量对象 ,就不列举了.
      而 内部函数的作用域链 就是 [ {内部函数_活动对象},{外部函数_活动对象},{全局对象} ]
     
      了解了 活动对象 , 就明白下面的函数为什么出现全都是返回10的函数
      function createFuncs(){
        var result = new Array();
     
        for(var i = 0 ; i < 10 ; i++){
          result[i] = function(){
            return i ;
          };
        }
        return result ;
      }
     
      createFuncs()[0](); // display 10
     
      其实把 活动对象的概念加上 , 就可以把上面代码解读成 下面的代码:
      function createFuncs(){
        var _outer = this ;
        _outer.result = new Array();
     
        for(_outer.i = 0 ; _outer.i < 10 ; _outer.i ++){
          result[_outer.i] = function(){
            return _outer.i ; // 这里可是使用的引用哦 , 而不是复制了i的值.是通过引用去找值.
          }
        }
        return _outer.result ;
      }
      createFuncs()[0](); // display 10
     
      也就是说 , 变量i 实际是外部函数的 活动对象 的属性i ,
      那么所有引用了这个 活动对象的属性i的实例,都会随着这个值变化(无论循环多少次,它们都是指向同一个对象属性)
      那么当i++后,外部函数 的这个活动对象的属性i的值会增加,所有使用了这个对象属性的内容都会变化成属性最终的值.
     
      想要 这段程序符合预期 , 可以改成如下模样 :
      function createFuncs(){
        var result = new Array();
        for(var i = 0 ; i< 10 ; i++){
     
          result[i] = function(num){
            console.log(num);
            return function(){
              return num ;
            }
          }(i);
     
        }
      }
     
      其实上面代码,就是利用了"基本类型数据 传递参数是按值传递" 的特性,
      当i或者说_out.i 传递给 num参数时 , 做了一次值的copy, 变成了内部函数的变量值
      所以最后结果就可以是复合预期的函数数组了.
     
     
    闭包中 this 的问题
     
      var name = "window name";
     
      var person = {
        name : "person name",
        getName : function(){
          return function(){
            return this.name ;
          };
        }
      }
     
      person.getName()(); // display 'window name'
      为什么会如此呢?
        内部函数的作用域链中,this是指内部函数的活动对象
        它会把外部函数的活动对象--也叫this给屏蔽掉,这是作用域链搜索机制导致的,本身有,就不会向上找
        那么person.getName() 返回了一个函数 , 再调用这个函数,这个调用环境已经变成了全局环境中调用
        在浏览器中,this自然指向window对象.
     
      想要按预期工作,可改为:
        var name = "window name";
     
        var person = {
          name : "person name",
          getName : function(){
            var _this = this ;
            return function(){
              return _this.name ;
            };
          }
        }
     
        person.getName()(); // display 'person name'
        这是由于,_this是作为外部函数活动对象的属性存在了,也就是变量嘛
        内部函数可以访问到这个外部变量,并持有它.
     
     
    闭包引起的内存泄露
      function addEvent(){
        var element = document.getElementById("el");
     
        element.onclick = function(){
          console.log(element.id);
        };
      }
     
      由于绑定的事件函数 是个闭包函数(也是匿名的),它保有了外部活动对象的一个引用.
      只要闭包函数在外界使用了,那么对于"利用引用计数法"来回收垃圾的浏览器,比如万恶的IE(早期),
      那么,引用数至少为1,造成无法回收.
      可以对上面代码做以下优化: (这个方法,本人没有细细考虑,转述作者的说法而已)
        function addEvent(){
          var element = document.getElementById("el");
          var eleId = element.id;
     
          element.onclick = function(){
            console.log(eleId);
          };
          element = null ;
        }
      把内部函数使用的引用,放到外部函数的变量中使用.
      将element设置为null,切断内部函数对它的持有.
     
     
    模拟块级作用域
      (function(){
        // 这里是 块级作用域
      })();
     
      讲一下这个结构,实际上,第一个括号内是一个 函数表达式,只不过是个匿名的.
      var func = function(){}; // 看到了没, 在这里 (function(){}) 其实就是 func啊
      func();
      之所以不能直接 function(){}(); --会报错
      是由于 function a(){} 不可以直接调用一样,解析器认为你在声明函数,说白了就是解析器不认它.
      那么加了括号了呢 (function(){}()) , 解析器会认为它是个 表达式,因为里面是函数,自然就是函数表达式了
     
      这种用法一般用在全局作用域中,防止命名冲突,而且执行完了也方便回收.
      不过其实什么地方都可以使用它
      function test(){
        (function(){
          for(var i = 0 ; i<10;i++){}
        })();
        console.log(i); // 报错,i is not defined
      }
      
     
    私有变量
      当前作用域中的所有变量,内部函数,以及 当前函数的参数 都可以看做是私有变量.
      而有权访问所有私有变量以及私有函数的方法 称为 特权方法.
      特权方法有两种定义方式 :
        利用构造函数 以及 利用静态私有变量
      
      示例:
        function MyObject(){
          var count = 0;
     
          function inner(){}
     
          // 特权方法 -- 绑定this了
          this.privilegeMethod = function(){
            count ++;
            return inner();
          }
        }
     
      好处是,可以隐藏那些不应该直接被修改的数据:
        function Person(name){
          this.getName = function(){
            return name ;
          };
     
          this.setName = function(value){
            name = value ;
          };
        }
        var person = new Person("origin");
        person.getName();
        person.setName("new name");
     
      坏处就是,所有实例没有共享这些方法,没有抽象出去.
      下面看看利用静态私有变量是如何解决这个问题的.
      (function(){
        //创建 共享变量 -- 所有对象实例共享.
        var name = "共享变量";
        //构造函数 , 使用函数表达式,而不是函数声明 -- 不想创建局部的.同样的原因,所以没有用var来声明,为了添加到全局.在严格模式下会报错.
        Person = function(value){
          name = value ;
        };
        // 公有/特权 方法
        Person.prototype.getName = function(){
          return name ;
        };
        Person.prototype.setName = function(value){
          name = value ;
        }
      })();
     
      var p = new Person("p");
      p.getName();
     
      这个模式的问题是,全都是静态变量..实例之间会互相影响.
     
     
    模块模式
      一般使用这个技巧是为了建立一个全局的,单例的实例来管理程序应用级的信息.
      所以它虽然用instanceof检查是Object类型的,也没所谓.不会用于传参给函数,也就无需检查类型.
     
      示例:
        var application = function(){
          // 私有变量 及 函数
          var components = new Array();
     
          // 初始化
          components.push(new BaseComponent());//不要在意BaseComponent是什么
     
          // 公共方法 -- 特权方法
          return {
            getComponents : function(){
              return components ;
            },
            registerComponent:function(component){
              if(typeof component === 'object'){
                components.push(component);
              }
            }
          };
        }();
     
     
    增强的模块模式:
      主要是为了使其是特定的对象,而不仅仅是Object类型.
      var application = function(){
        var name = '';
        var components = new Array();
        components.push(new BaseComponent());
     
        var app = new BaseComponent(); // 不再直接return 字面量对象,而是让它称为具体的类型
     
        app.getComponentCnt = function(){
          return components.length;
        };
        app.registerComponent = function(component){
          if(typeof component === 'object'){
            components.push(component);
          }
        };
        return app ;
      };
     
     
     
     
     
  • 相关阅读:
    apktool 在mac下的使用 -反编译安卓apk文件
    通过Stetho在Chrome上调试Android App
    Android Studio 遇到 No Debuggable Applications 的解决方案
    安装Eclipse Maven插件的方法
    Android如何实现点击一次返回键返回桌面而不是退出应用
    安卓7.0遇到 android.os.FileUriExposedException: file:///storage/emulated.. exposed beyond app through Intent.getData()
    由Memcached使用不当而引发性能问题的两个经验总结
    对MySql查询缓存及SQL Server过程缓存的理解及总结
    由Java中toString()方法引发的无意识的递归想到的
    为什么不能把委托(delegate)放在一个接口(interface)当中?
  • 原文地址:https://www.cnblogs.com/lmxxlm-123/p/11131843.html
Copyright © 2020-2023  润新知