本文深入解析JavaScript中匿名函数与闭包的核心概念。详细阐述匿名函数的定义与自执行方式,剖析闭包的形成机制及其在变量持久化中的应用。通过对比全局变量与局部变量,说明闭包在避免命名冲突和实现数据封装上的优势。同时,针对循环中匿名函数取值错误、this指向丢失及内存泄漏等常见陷阱,提供了基于闭包和立即执行函数的优化方案,帮助开发者掌握高级JS编程技巧。
概述
概述
在前端JavaScript开发中,函数与其创建时的词法环境(lexical environment)引用共同构成了闭包(closure)。这意味着内部函数可以访问外部函数的作用域。在JavaScript中,每次创建函数时都会生成闭包。将匿名函数与闭包结合学习,有助于加深理解。本文通过几个简单的示例,简要介绍匿名函数和闭包的常见用法,旨在供学习参考,如有疏漏之处,欢迎指正。
普通函数与匿名函数基础
普通函数
普通函数由function关键字、函数名、圆括号()以及一对花括号{}组成,示例如下:
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指向的问题
在对象内部,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对象,因此闭包运行时this指向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匿名函数和闭包的资料请关注本站其它相关文章!