详解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 相关文章推荐
Javascript 读后台cookie代码
Sep 15 Javascript
node.js适合游戏后台开发吗?
Sep 03 Javascript
JS结合bootstrap实现基本的增删改查功能
Jul 22 Javascript
js简单时间比较的方法
Aug 02 Javascript
HTML页面,测试JS对C函数的调用简单实例
Aug 09 Javascript
微信开发 消息推送实现代码
Oct 21 Javascript
JavaScript中的高级函数
Jan 04 Javascript
vue 实现剪裁图片并上传服务器功能
Mar 01 Javascript
JavaScript实现微信号随机切换代码
Mar 09 Javascript
详解mpvue中小程序自定义导航组件开发指南
Feb 11 Javascript
Vue Prop属性功能与用法实例详解
Feb 23 Javascript
操作按钮悬浮固定在微信小程序底部的实现代码
Aug 02 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
PHP网页游戏学习之Xnova(ogame)源码解读(十六)
2014/06/30 PHP
Javascript解决常见浏览器兼容问题的12种方法
2010/01/04 Javascript
jQuery学习基础知识小结
2010/11/25 Javascript
利用js的Node遍历找到repeater的一个字段实例介绍
2013/04/25 Javascript
Nodejs实现的一个静态服务器实例
2014/12/06 NodeJs
jquery插件qrcode在线生成二维码
2015/04/26 Javascript
JS获取下拉框显示值和判断单选按钮的方法
2015/07/09 Javascript
Node.js Addons翻译(C/C++扩展)
2016/06/12 Javascript
JS中递归函数
2016/06/17 Javascript
Node.js + Redis Sorted Set实现任务队列
2016/09/19 Javascript
浅谈vue2 单页面如何设置网页title
2017/11/08 Javascript
Vue自定义指令封装节流函数的方法示例
2018/07/09 Javascript
vue动态改变背景图片demo分享
2018/09/13 Javascript
Vue Element UI + OSS实现上传文件功能
2019/07/31 Javascript
Vue + Element UI图片上传控件使用详解
2019/08/20 Javascript
vue 使用鼠标滚动加载数据的例子
2019/10/31 Javascript
js实现日历
2020/11/07 Javascript
详解Python中的array数组模块相关使用
2016/07/05 Python
Python3.6简单反射操作示例
2018/06/14 Python
基于python实现蓝牙通信代码实例
2019/11/19 Python
3种python调用其他脚本的方法
2020/01/06 Python
Python统计文本词汇出现次数的实例代码
2020/02/27 Python
基于python实现音乐播放器代码实例
2020/07/01 Python
美国地毯购买网站:Rugs USA
2019/02/23 全球购物
索尼巴西商店:Sony巴西
2019/06/21 全球购物
高校教师思想汇报
2014/01/11 职场文书
大学生暑期实践感言
2014/02/26 职场文书
美术指导求职信
2014/03/17 职场文书
保护环境倡议书
2014/04/14 职场文书
国际贸易求职信
2014/07/05 职场文书
项目申报专员岗位职责
2014/07/09 职场文书
乡镇保密工作责任书
2014/07/28 职场文书
幼儿教师辞职信范文
2015/03/02 职场文书
预备党员介绍人意见
2015/06/01 职场文书
单身证明范本
2015/06/15 职场文书
Python中异常处理用法
2021/11/27 Python