函数表达式 及 闭包

函数表达式
  函数又两种定义方式:
    ①.函数声明
      它有个特性叫 函数声明提升.可以把这个声明放到最后面,前面调用不会报错.
        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 ;
  };
 
 
 
 
 
posted @ 2019-07-04 13:19  豆豆飞  阅读(404)  评论(0编辑  收藏  举报