JavaScript 匿名函数和闭包介绍


Posted in Javascript onApril 13, 2015

匿名函数:没有名字的函数;
闭包:可访问一个函数作用域里的变量的函数;

一 匿名函数

// 普通函数
  function box(){            // 函数名是box;
    return 'Lee';           
  }
  box();                // =>Lee; 调用函数;
// 匿名函数
  function(){              // 匿名函数,会报错;
    return 'Lee';
  }
// 通过表达式自我执行
  (function(name){
    console.log(name);        // =>Lee;
  })("Lee");              // "()"表示执行函数,并且可以传参;
// 把匿名函数赋值给变量
  var box = function(){        // 将匿名函数赋给变量;
    return 'Lee';
  };
  console.log(box());         // 调用方式和函数调用相似;
// 函数里的匿名函数
  function box(){
    return function(name){      // 函数里的匿名函数,产生闭包;
      return name;
    };
  };
  console.log(box()("Lee"));      // 函数box()调用匿名函数,并传参;

二 闭包

闭包:有权访问另一个函数作用域中的变量的函数;
创建闭包的常见方式:在一个函数内部创建另一个函数;通过另一个函数访问这个函数的局部变量;

// 通过闭包可以返回局部变量
  function box(){
    var user = 'Lee';
    return function(){        // 通过匿名函数返回box()的局部变量user;
      return user;
    };
  }
  console.log(box()());        // =>Lee; 直接调用box()()来获得匿名函数的返回值;

  var b = box();
  console.log(b());          // =>Lee; 另一种调用匿名函数方式;

// 优点:可以把局部变量驻留在内存中,可以避免使用全局变量; 
// (全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难;所以推荐使用私有的,封装的局部变量);
// 缺点:
// 通过全局变量来累加
  var age = 100;            // 全局变量;
  function box(){
    age++;              // 模块级可以调用全局变量,进行累加;
  };
  box();                // 执行函数,累加一次;
  console.log(age);           // =>101; 输出全局变量;
  box();                // 执行函数,累加一次;
  console.log(age);           // =>102; 输出全局变量;
// 通过局部变量无法实现累加
  function box(){
    var age = 100;
    age++;              // 实现累加;
    return age;
  }
  console.log(box());          // =>101;
  console.log(box());          // =>101; 无法实现累加,因为第二次调用函数时,函数内部变量age又被初始化了;

// 通过闭包可以实现局部变量的累加
  function box(){
    var age = 100;
    return function(){        // 匿名函数内实现累加;
      age++;
      return age;          // 并返回累加后的变量; 
    };                // 此时box()函数的局部变量age的值已经被修改为累加后的值;
  }
  var b = box();            // 给box()函数赋值给变量;
  console.log(b());           // =>101; 调用匿名函数,累加一次;
  console.log(b());           // =>102; 第二次调用匿名函数,累加两次;

// PS:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存;所以过度使用闭包会导致性能下降;(将闭包引用在"私有作用域"中即可实现变量销毁)
// 作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值; ?
// 循环里包含匿名函数
  function box(){
    var arr = [];
    for(var i=0; i<5; i++){     // 当声明变量i=5时,循环停止;而此时循环里的变量i==5; 
      arr[i] = function(){    // arr[i]得到的只是没有执行的匿名函数function(){};
        return i;        
      };
    };
    return arr;           // arr = [function,function,function,function,function];
  }
  var b = box();           // =>[function,function,function,function,function]; 得到函数box()返回的数组arr;
  console.log(b.length);       // =>5; 得到函数集合数组长度;
  for(var i=0; i<b.length; i++){
    console.log(box()[i]());    // =>5,5,5,5,5; 输出每个函数的值,都是最后一个值;
  }
  // 上面的例子输出的结果都是5,也就是循环后得到的最大i值;
  // 因为b[i]调用的是匿名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,i早已变成5;

// 循环里包含匿名函数-改1,自我执行匿名函数
  function box(){
    var arr = [];
    for(var i=0; i<5; i++){
      arr[i] = (function(num){  // arr[i]得到的是匿名函数执行后的结果数值0-4;
        return num; 
      })(i);           // 自我执行并传参;
    }
    return arr; 
  }
  var b = box();           // =>[0,1,2,3,4]; 此时b代表box()返回的数组;
  for (var i = 0; i < b.length; i++) {
    console.log(b[i]);       // 0 1 2 3 4; 这里返回的是数值;
  };
  // 例子中,我们让匿名函数进行自我执行,导致最终返回给a[i]的是数组而不是函数了;最终导致b[0]-b[4]中保留了0,1,2,3,4的值;

// 循环里包含匿名函数-改2,匿名函数里再做个匿名函数;
  function box(){
    var arr = []; 
    for(var i=0; i<5; i++){
      arr[i] = (function(num){
        return function(){   // 返回函数;
          return num;      
        }
      })(i);
    }
    return arr;           // arr = [function,function,function,function,function];
  }
  var b = box();
  for (var i = 0; i < b.length; i++) {
    console.log(b[i]());      // 0,1,2,3,4; 
  };

// 改1和改2中,我们通过匿名函数自我执行,立即把结果赋值给arr[i];
// 每一个i,是调用方通过按值传递的,所以最终返回的都是指定的递增的i;而不是box()函数中的变量i;

三 this对象

// 在闭包中使用this对象可能会导致一些问题;this对象是在运行时基于函数的执行环境绑定的;
// 如果this在全局范围就是指向window,如果在对象内部就指向这个对象;
// 而闭包却在运行时指向window的,因为闭包并不属于这个对象的属性或方法; 
  var user = 'Window';
  var obj = {
    user:'Object',
    getUserFunction:function(){
      return function(){            // 闭包不属于obj,里面的this指向window;
        return this.user;
      };
    }
  };
  console.log(obj.getUserFunction()());      // =>Window;

  // 可以强制指向某个对象
  console.log(obj.getUserFunction().call(obj));  // =>Object;

  // 也可以从上一个作用域中的得到对象
  getUserFunction:function(){
    var that = this;               // 从对象的方法里得到this;此时that指向obj对象;
    return function(){
      return that.user;
    }
  }
  console.log(obj.getUserFunction()());      // =>Object;

四 内存泄漏

// 由于IE的JScript对象和DOM对象使用不同的垃圾收集方式,因此闭包在IE中会导致内存泄漏问题,也就是无法销毁驻留在内存中的元素;
  function box(){
    var oDiv = document.getElementById('oDiv'); // oDiv用完之后一直驻留在内存中;
    oDiv.onclick = function(){
      alert(oDiv.innerHTML);          // 这里用oDiv导致内存泄漏;
    };
    oDiv = null;                 // 解除引用;
  }
  box();
  // 由于匿名函数保存了一个对box()的活动对象的引用,因此就会导致无法减少oDiv的引用数;
  // 只要匿名函数存在,oDiv的引用数至少也是1;因此它所占用的内存就永远不会被回收;
  // PS:如果没有使用解除引用,那么要等到浏览器关闭才得以释放;

五 模仿块级作用域(定义并立即调用一个匿名函数)

// JS没有块级作用域的概念;
// 这意味着在块语句(for语句/if语句)中定义的变量,实际上是在包含函数中而非语句中创建的;
  function box(count){
    for(var i=0; i<count; i++){}        // box(2); => count=2; i=2时循环停止,此时i=2;
    console.log(i);               // =>2; i不会因为离开了for块就失效;
  }
  box(2);

  function box(count){
    for(var i=0; i<count; i++){}
    var i;                   // 就算重新声明,也不会覆盖前面的值;
    console.log(i);
  }
  box(2);
// 在JavaScript中,变量i是定义在box()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它;
// 以上两个例子,说明JavaScript没有块级语句的作用域,if(){}/for(){}等没有作用域;
// 如果有作用域的话,出了这个范围i就应该被销毁;

// JavaScript不会提醒是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见(如果是初始化并赋值,还是会执行的);

// 模仿块级作用域(私有作用域)
  (function(){
    // 这里是块级作用域;
  })();
  // 以上代码定义并立即调用了一个匿名函数;将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式;

// 使用块级作用域(私有作用域)改写
  function box(count){
    (function(){
      for(var i=0; i<count; i++){}
    })();
    console.log(i);                // 报错,无法访问;变量i在私有作用域中,出了私有作用域即被销毁了.
  }
  box(2);
// 使用了块级作用域后,匿名函数中定义的任何变量,都会在执行结束时被销毁;(i只能在循环中使用,使用后即被销毁);
// 而私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,他能够访问包含作用域中的所有变量;
// 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数;
// 一般来说,我们都应该尽可能少向全局作用域中添加变量和函数;过多的全局变量和函数很容易导致命名冲突;
// 使用块级作用域,每个开发者既可以使用自己的变量,又不必担心搞乱全局作用域;
  (function(){
    var box = [1,2,3,4];
    console.log(box);              // =>[1,2,3,4]; box出来就不被认识了;
  })();                      // 销毁匿名函数中的变量;
  console.log(box);                // =>box is not defined;
  // 在全局作用域中使用块级作用域可以减少闭包占用的内存问题;因为没有指向匿名函数的引用
  // 只要函数执行完毕,就可以立即销毁其作用域链了;

六 私有变量

// JavaScript没用私有属性的概念;所有的属性都是公用的;
// 不过有一个私有变量的概念:在任何函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量;
// 私有变量包括函数的参数/局部变量和在函数内部定义的其他函数;
  function box(){
    var age = 100;                 // 私有变量,外部无法访问;
  }

// 而通过内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量;
// 而利用这一点,可以创建用于访问私有变量的公用方法;特权方法;
  function Box(){                  // 构造函数;
    var age = 100;                 // 私有变量;
    function run(){                // 私有函数;
      return '运行中...';
    };
    this.get = function(){             // 对外公共的特权方法;
      return age+run();             // 将闭包赋值给变量;
    };
  }
  var box = new Box();
  console.log(box.get());

// 可以通过构造方法传参来访问私有变量
  function Person(name){
    var user = name;              // 这句其实可以省略;
    this.getUser = function(){
      return user;
    };
    this.setUser = function(name){
      user = name;
    }
  }
  var p = new Person('Lee');
  console.log(p.getUser());            // =>Lee;
  console.log(p.setUser('Jack'));
  console.log(p.getUser());            // =>Jack;
  // 但是,构造函数模式的缺点是针对每个实例都会创建同样一组新方法;而使用静态私有变量来实现特权方法就可以避免这个问题;

七 静态私有变量

// 通过块级作用域(私有作用域)中定义私有变量或函数,同样可以创建对外公共的特权方法;
  (function(){                  // 创建私有作用域;
    var age = 100;               // 静态私有变量;
    function run(){
      return '运行中...';
    };
    Box = function(){};             // 使用函数表达式定义构造函数;
    Box.prototype.go = function(){       // 公有(特权)方法;在原型上定义的;
      return age+run();
    };
  })();
  var box = new Box();
  console.log(box.go());             // 100运行中...;
// 上面的对象声明,采用的是Box = function(){}而不是functiong Box(){};并且在声明Box时没有使用var关键字
// 导致:初始化未经声明的变量,总是会创建一个全局变量;因此,Box就成了一个全局变量,能够在私有作用域之外被访问到;
// 因为如果用函数声明定义构造函数,那么就变成私有函数了,无法在全局访问到了,所以要使用函数式定义构造方法;
  (function(){
    var user = "";
    Person = function(value){          // 此处定义的Person是全局变量;
      user = value;              // 这里的构造函数有权访问私有变量name;
    };
    Person.prototype.getUser = function(){
      return user;
    };
    Person.prototype.setUser = function(value){
      user = value;
    }
  })();
  var person = new Person();
  person.setUser('Lee');
  console.log(person.getUser());          // =>Lee;
// 使用了prototype导致方法共享了,而user也就变成静态属性了;
// 所谓静态属性:即共享于不同对象中的属性;?

八 模块模式

// 简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式;
// 之前采用的都是构造函数的方式来创建私有变量和特权方法;
// 那么对象字面量方式就采用模块模式来创建;
  var box = {                   // 字面量对象,也是单例对象:只有一个实例的对象;
    age:100,                   // 这是公有属性,将要改成私有;
    run:function(){
      return '运行中...';
    };
  };

// 模块模式私有化变量和函数:
  var box = function(){
    var age = 100;
    function run(){
      return '运行中...';
    }
    return {                   // 将一个字面量对象作为函数的值返回;
      go:function(){              // 返回的对象字面量中只包含可以公开的属性和方法;
        return age+run();          // 由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数;
      }                  
    };                      // 从本质上讲,这个对象字面量定义的是单例的公共接口;
  }();
// 这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的; 

// 上面直接返回对象的例子,也可以这么写:
  var box = function(){
    var age = 100;
    function run(){
      return '运行中...';
    }
    var obj = {                  // 创建字面量对象;
      go:function(){
        return age+run();
      }
    };
    return obj;                  // 返回刚创建的对象;
  }();

// 字面量的对象声明,其实在设计模式中可以看作是一种单例模式;
// 所谓单例模式,就是永远保持对象的只有一个实例;

// 增强的模块模式:适合返回自定义对象,也就是构造函数;
  function Desk(){};
  var box = function(){
    var age = 100;
    function run(){
      return '运行中...';
    };
    var desk = new Desk();
    desk.go = function(){
      return age+run();
    };
    return desk;
  }();
  console.log(box.go());              // =>100运行中;

九 小结

在JavaScript编程中,函数表达式是一种非常有用的技术;使用函数表达式可以无须对函数命名,从而实现动态编程;

1.函数表达式

函数表达式不同于函数声明;函数声明要求有名字,但函数表达式不需要;
没有名字的函数表达式叫做匿名函数;2.闭包
当在函数内部定义了其他函数时,就创建了闭包.闭包有权访问包含函数内部的所有变量;原理如下:
在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域;
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁;
但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止;3.块级作用域
使用闭包可以在JavaScript中模仿块级作用域(JavaScript本身没有块级作用域的概念);要点如下:
创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用;
结果就是函数内部的所有变量都会被立即销毁--除非将某些变量赋值给了包含作用域(即外部作用域)中的变量;4.私有变量
闭包还可以用于在对象中创建私有变量,要点如下:
即使JavaScript中没有真是的私有对象属性的概念,但是可以使用闭包来实现公有方法,而通过公有方法可以访问包含作用域中定义的变量;
可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式来实现单例的特权方法;

Javascript 相关文章推荐
javascript Firefox与IE 替换节点的方法
Feb 24 Javascript
jquery.boxy弹出框(后隔N秒后自动隐藏/自动跳转)
Jan 15 Javascript
JS中令人发指的valueOf方法介绍
Feb 22 Javascript
Javascript基础 函数“重载” 详细介绍
Oct 25 Javascript
老生常谈jquery id选择器和class选择器的区别
Feb 12 Javascript
js使用html2canvas实现屏幕截取的示例代码
Aug 28 Javascript
webpack学习教程之前端性能优化总结
Dec 05 Javascript
使用classList来实现两个按钮样式的切换方法
Jan 24 Javascript
微信小程序之分享页面如何返回首页的示例
Mar 28 Javascript
Vue插件打包与发布的方法示例
Aug 20 Javascript
详解JavaScript事件循环机制
Sep 07 Javascript
解决vux 中popup 组件Mask 遮罩在最上层的问题
Nov 03 Javascript
jfreechart插件将数据展示成饼状图、柱状图和折线图
Apr 13 #Javascript
为什么JS中eval处理JSON数据要加括号
Apr 13 #Javascript
使用window.prompt()实现弹出用户输入的对话框
Apr 13 #Javascript
jsMind通过鼠标拖拽的方式调整节点位置
Apr 13 #Javascript
javascript继承的六大模式小结
Apr 13 #Javascript
javascript制作的简单注册模块表单验证
Apr 13 #Javascript
简化版手机端照片预览组件
Apr 13 #Javascript
You might like
zf框架的db类select查询器join链表使用示例(zend框架)
2014/03/14 PHP
PHP利用APC模块实现大文件上传进度条的方法
2015/10/29 PHP
php数据访问之增删改查操作
2016/05/09 PHP
Yii2 RESTful中api的使用及开发实例详解
2016/07/06 PHP
php编程实现简单的网页版计算器功能示例
2017/04/26 PHP
php查询内存信息操作示例
2019/05/09 PHP
javascript 屏蔽鼠标键盘的几段代码
2008/01/02 Javascript
Prototype Template对象 学习
2009/07/19 Javascript
基于jquery的$.ajax async使用
2011/10/19 Javascript
js 本地预览的简单实现方法
2014/02/18 Javascript
AngularJS ng-change 指令的详解及简单实例
2016/07/30 Javascript
老生常谈Javascript中的原型和this指针
2016/10/09 Javascript
探索webpack模块及webpack3新特性
2017/09/18 Javascript
Angular实现搜索框及价格上下限功能
2018/01/19 Javascript
详解ES6 系列之异步处理实战
2018/10/26 Javascript
微信小程序实现购物车代码实例详解
2019/08/29 Javascript
微信小程序实现滑动操作代码
2020/04/23 Javascript
Python连接PostgreSQL数据库的方法
2016/11/28 Python
基于并发服务器几种实现方法(总结)
2017/12/29 Python
python实现Flappy Bird源码
2018/12/24 Python
Python2和Python3之间的str处理方式导致乱码的讲解
2019/01/03 Python
python如何读取bin文件并下发串口
2019/07/05 Python
python实现图片上添加图片
2019/11/26 Python
Python进程间通信multiprocess代码实例
2020/03/18 Python
tensorflow转换ckpt为savermodel模型的实现
2020/05/25 Python
css3实例教程 一款纯css3实现的发光屏幕旋转特效
2014/12/07 HTML / CSS
CSS3 mask 遮罩的具体使用方法
2017/11/03 HTML / CSS
创业计划书的内容步骤和要领
2014/01/04 职场文书
小学班主任评语大全
2014/04/23 职场文书
会计电算化专业求职信
2014/06/10 职场文书
2014年财务科工作总结
2014/11/11 职场文书
工程安全生产协议书
2014/11/21 职场文书
领导干部学习十八届五中全会精神心得体会
2016/01/05 职场文书
Navicat for MySQL的使用教程详解
2021/05/27 MySQL
MySQL表锁、行锁、排它锁及共享锁的使用详解
2022/04/02 MySQL
Windows Server 2008 修改远程登录端口以及配置防火墙
2022/04/28 Servers