详解JavaScript匿名函数和闭包


Posted in Javascript onJuly 10, 2020

概述

在JavaScript前端开发中,函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。匿名函数和闭包可以放在一起学习,可以加深理解。本文主要通过一些简单的小例子,简述匿名函数和闭包的常见用法,仅供学习分享使用,如有不足之处,还请指正。

普通函数

普通函数由fucntion关键字,函数名,() 和一对{} 组成,如下所示:

function box(){
   return 'Hex';
 }
 alert(box());

匿名函数

顾名思义,匿名函数就是没有实际名字的函数。单独的匿名函数无法运行,如下所示:

function (){
   return 'Hex';
 }
 //以上,会报错:缺少标识符

如何解决匿名函数不能执行的问题呢?有如下几种方法:

1. 把匿名函数赋值给变量,如下所示:

//把匿名函数赋值给变量
 var box=function(){
   return 'Hex';
 }
 alert(box());

2. 通过自我执行来调用函数,格式如下:(匿名函数)()

(function(){
   alert('Hex');
 })();

3. 把匿名函数自我执行的返回值赋值给变量,如下所示:

var box=(function(){
   return 'Hex';
 })();
 alert(box);//注意:此处不带括弧

4. 或者省去变量,如下所示:

alert((function() {
   return 'Hex';
 })());

自我执行匿名函数如何传递参数呢?如下所示:

(function(age) {
   alert('Hex--' + age);
 })(30);

闭包(closure)

闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。简单理解:函数里面套函数,子函数可以访问父函数的作用域里面的变量。

1. 函数里面放匿名函数,如下所示:

function box(){
  //闭包
  return function(){
    return 'Hex';
  }
}
alert(box()());
//或者
var b=box();
alert(b());

2. 通过闭包返回局部变量,使用闭包可以有一个优点,和是它的缺点,可以是局部变量驻留在内存中。

function box(){
  var age=100;//此变量为函数的局部变量,外部无法访问
  return function(){
    return age;
  }
}
alert(box()());

闭包和全局变量相比较

1. 使用全局变量累加,如下所示:

var age=100;
function box(){
  age++;
}
alert(age);
box();
alert(age);
box();
alert(age);

2. 使用局部变量累加,如下所示:

function box(){
  var age=100;
  age++;
  return age;
}
alert(box());//无法实现累加
alert(box());//无法实现累加
alert(box());//无法实现累加

3. 使用闭包实现累加,如下所示:

function box(){
  var age=100;
  return function(){
    age++;
    return age;
  }
}
var b=box();//将返回值赋值给b
alert(b());//实现累加
alert(b());//实现累加
alert(b());//实现累加
b=null;//使用闭包在调用结束时不会立即销毁内存,导致性能下降,所以需要解除占用

差异:使用全局变量,容易引起命名冲突,且系统性能下降。

循环匿名函数取值问题

1. 循环里的匿名函数取值问题,如下所示:没有实现arr[0]=0,arr[1]=1 ...arr[4]=4的效果

function box(){
  var arr=[];
  for (var i=0;i<5;i++) {
    arr[i]=function(){
      return i;
    }
  }
  //函数返回之前,循环已经结束,i=5
  return arr;
}
var b=box();
for (var i=0;i<5;i++) {
  alert(b[i]()); //此时返回的都是5,没有实现arr[0]=0,arr[1]=1 ...arr[4]=4的效果
}

以上问题如何优化呢?

方法1,直接赋值,不采用闭包,如下所示:

function box(){
  var arr=[];
  for (var i=0;i<5;i++) {
    arr[i]=i; //直接赋值
  }
  //函数返回之前,循环已经结束,i=5
  return arr;
}
var b=box();
for (var i=0;i<5;i++) {
  alert(b[i]);
}

方法2,通过匿名函数的自我执行,如下所示:

function box(){
  var arr=[];
  for (var i=0;i<5;i++) {
    arr[i]=(function(num){
    //此处可以有其他一些逻辑
    
return num;
    })(i);
  }
  return arr;
}
var b=box();
for (var i=0;i<5;i++) {
  alert(b[i]);
}

方法3,将变量驻留在内存中,如下所示:

function box(){
  var arr=[];
  for (var i=0;i<5;i++) {
    arr[i]=(function(num){
      //此处可以有其他一些逻辑
      return function(){
      return num;
      };
    })(i);
  }
  return arr;
}
var b=box();
for (var i=0;i<5;i++) {
  alert(b[i]());
}

关于this的指向问题

对于对象内部,this指向对象本身,如下所示:

var box={
   getThis:function(){
     return this;
   }
 };
 alert(box.getThis());//输出[object Object] //此处this指box对象
var user='The window';
var box={
  user:'The box',
  getUser:function(){
    return this.user;
  }
}
alert(box.getUser());//输出:the box

this在闭包中,指示window对象,所以闭包在运行时指向window,如下所示:

var box1 ={
  getThis:function(){
    return function(){
      return this;
    }
  }
};
alert(box1.getThis()()); //输出[object Window]//此处this是window对象
var box1={
  user:'The box',
  getUser:function(){
    //此处的作用域是box1
    return function(){
      //此处的作用域是widow
      return this.user;
    };
  }
}
alert(box1.getUser()());//输出:the window ,表示闭包在运行时模拟this指向window

如何让闭包的this指向box呢?可以有如下两种方法,如下所示:

alert(box1.getUser().call(box1));//对象冒充
//可以将box的作用域对象传递给闭包
var box1={
  user:'The box',
  getUser:function(){
    var that=this;
    return function(){
      return that.user;
    };
  }
}
alert(box1.getUser()());

缺点:闭包无法释放对象,容易导致内存泄漏,如下所示:

function box(){
  var a1=document.getElementById('A01');
  var txt=a1.innerHTML;
  a1.onclick=function(){
    //如果a1为null,则会报错
    //alert(a1.innerHTML);//点击事件获取内容,
    alert(txt);
  }
  //如无下面一句,则会导致内存无法释放对象a1
  a1=null;//此处需要手动将a1释放,等待回收
}
box();

块级作用域

模仿块级作用域,面向对象的思想,封装变量。普通函数没有块级作用域的概念,如下所示:

function box(){
  for (var i=0;i<5;i++) {

  }
  alert(i);//输出:5,表示出了for语句块,i依然可以访问
}
box();

如何让i私有化,出了作用域,不可以访问呢?可以采用匿名函数的自我执行,则出了作用域就会访问不到,如下所示:

function box(){
  (function(){
    for (var i=0;i<5;i++) {

    }
  })();
  //alert(i);//报错:提示“i”未定义
}
box();

全局变量的私有作用域,减少变量的命名冲突,如下所示:

(function(){
   //此处就是全局作用域里面的私有作用域
   var age=100;
   alert(age);
 })();
 //alert(age);////报错:提示“age”未定义

普通函数和构造函数的区别:首字母大写。如下所示:对象的属性和函数都是public类型的

function Box(){
  this.age=100; //此处是公有属性,无法私有化
  //函数也是公有函数
  this.run=function(){
    return 'running....';
  }
}
var box=new Box();
alert(box.age); //通过对象可以访问
alert(box.run());//通过对象可以访问

如何将公有属性,私有化呢? 如下所示:

function Box(){
  var age=100;//私有变量,外部访问不到
  function run(){//私有函数,外部访问不到
    return 'running....';
  }
  //对外公布的访问接口,可以访问私有内容
  this.go=function(){
    return age+' '+run();
  }
}
var box=new Box();
alert(box.go());

通过构造函数传递参数,如下所示:

function Box(v){
  var user=v;
  this.getUser=function(){
    return user;
  };
  this.setUser=function(v){
    user=v;
  }
}
var box=new Box('Hex');
alert(box.getUser());
//对象方法可以在创建的时候,创建多次

注意:通过构造函数创建对象,在每次创建的时候,都会分配不同的地址。

静态私有变量

采用静态私有变量,可以实现数据的共享,如下所示:

(function(){
  var user=''; //私有变量
  Box=function(value){//必须全局构造函数,将匿名函数赋值给Box,否则外部无法访问
    user=value;
  }
  Box.prototype.getUser=function(){
    return user;
  };
  Box.prototype.setUser=function(value){
    user=value;
  };
})();
var box=new Box('AAAA'); //第一次实例化
alert(box.getUser());//输出AAAA
var box2=new Box('BBBB');//第二次实例化
alert(box.getUser());//输出BBBB

单例对象

单例即只有一个实例化的对象,可以有两种实现方式。

1. 通过字面量的方式实现,如下所示:

var box={
  user:'hex',
  go:function(){
    return user+' is running....';
  }
};
alert(box.go());

2. 通过匿名函数的自我执行返回对象的方式实现,如下所示:

var box=function(){
  var user='Hex'; //私有变量
  function run(){ //私有函数
    return ' is running....';
  }
  //返回一个对象
  var obj= {
    //公共特权方法
    going:function(){
      return user+run();
    }
  }
  return obj;
}();
alert(box.going());

以上就是详解JavaScript匿名函数和闭包的详细内容,更多关于JavaScript匿名函数和闭包的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
基于Jquery的仿照flash放大图片效果代码
Mar 16 Javascript
最短的javascript:地址栏载入脚本代码
Oct 13 Javascript
Js(JavaScript)中,弹出是或否的选择框示例(confirm用法的实例分析)
Jul 09 Javascript
jQuery实用小技巧_输入框文字获取和失去焦点的简单实例
Aug 25 Javascript
js仿小米手机上下滑动效果
Feb 05 Javascript
js 事件的传播机制(实例讲解)
Jul 20 Javascript
js实现日期显示的一些操作(实例讲解)
Jul 27 Javascript
JS+canvas动态绘制饼图的方法示例
Sep 12 Javascript
vue微信分享 vue实现当前页面分享其他页面
Dec 02 Javascript
JS实现的类似微信聊天效果示例
Jan 29 Javascript
vue.js 子组件无法获取父组件store值的解决方式
Nov 08 Javascript
prettier自动格式化去换行的实现代码
Aug 25 Javascript
Vue watch响应数据实现方法解析
Jul 10 #Javascript
详解Vue之事件处理
Jul 10 #Javascript
jQuery开发仿QQ版音乐播放器
Jul 10 #jQuery
Element图表初始大小及窗口自适应实现
Jul 10 #Javascript
Vue路由切换页面不更新问题解决方案
Jul 10 #Javascript
简单了解Vue computed属性及watch区别
Jul 10 #Javascript
通过实例解析chrome如何在mac环境中安装vue-devtools插件
Jul 10 #Javascript
You might like
win7下memCache的安装过程(具体操作步骤)
2013/06/28 PHP
php预定义变量使用帮助(带实例)
2013/10/30 PHP
总结PHP中DateTime的常用方法
2016/08/11 PHP
PHP CURL post数据报错 failed creating formpost data
2016/10/16 PHP
PHP实现的最大正向匹配算法示例
2017/12/19 PHP
php设计模式之迭代器模式实例分析【星际争霸游戏案例】
2020/04/07 PHP
JavaScript 原型链学习总结
2010/10/29 Javascript
jQuery布局插件UI Layout简介及使用方法
2013/04/03 Javascript
js获取本机的外网/广域网ip地址完整源码
2013/08/12 Javascript
使用javascript实现ListBox左右全选,单选,多选,全请
2013/11/07 Javascript
jQuery使用empty()方法删除元素及其所有子元素的方法
2015/03/26 Javascript
JS实现简单的图书馆享元模式实例
2015/06/30 Javascript
jQuery Validate初步体验(二)
2015/12/12 Javascript
详解Angualr 组件间通信
2017/01/21 Javascript
详解如何用模块化的方式写vuejs
2017/12/16 Javascript
微信小程序获取位置展示地图并标注信息的实例代码
2019/09/01 Javascript
vue双向绑定数据限制长度的方法
2019/11/04 Javascript
vue+elementUI(el-upload)图片压缩,默认同比例压缩操作
2020/08/10 Javascript
Vue+Element自定义纵向表格表头教程
2020/10/26 Javascript
vue制作toast组件npm包示例代码
2020/10/29 Javascript
[01:39](回顾)各路豪强针锋相对,几经鏖战四强产生
2014/07/01 DOTA
python判断端口是否打开的实现代码
2013/02/10 Python
Python简单定义与使用字典dict的方法示例
2017/07/25 Python
在mac下查找python包存放路径site-packages的实现方法
2018/11/06 Python
基于Html5实现的react拖拽排序组件示例
2018/08/13 HTML / CSS
英国经典球衣网站:Classic Football Shirts
2017/05/20 全球购物
澳洲的UGG雪地靴超级市场:Uggs.com.au
2020/04/06 全球购物
软件测试企业面试试卷
2016/07/13 面试题
百日安全生产活动总结
2014/07/05 职场文书
2014年学校总务处工作总结
2014/12/08 职场文书
一年级语文上册复习计划
2015/01/17 职场文书
写作技巧:如何撰写一份优秀的营销策划书
2019/08/13 职场文书
Spring事务管理下synchronized锁失效问题的解决方法
2022/03/31 Java/Android
Java 关于String字符串原理上的问题
2022/04/07 Java/Android
Spring Data JPA框架自定义Repository接口
2022/04/28 Java/Android
redis protocol通信协议及使用详解
2022/07/15 Redis