JavaScript基础---作用域,匿名函数和闭包
匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数。
一.匿名函数
//普通函数
function box() { //函数名是 box
return 'TT';
}
//匿名函数
function () { //匿名函数,会报错
return 'TT';
}
//通过表达式自我执行
(function box() { //封装成表达式
alert('TT');
})(); //()表示执行函数,并且传参
//把匿名函数赋值给变量
var box = function () { //将匿名函数赋给变量
return 'TT';
};
alert(box()); //调用方式和函数调用相似
//函数里的匿名函数
function box () {
return function () { //函数里的匿名函数,产生闭包
return 'TT';
}
}
alert(box()()); //调用匿名函数
二.闭包
闭包是指有权访问另一个函数作用域中的变量的函数, 创建闭包的常见的方式, 就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
代码示例:
 //通过闭包可以返回局部变量
 function box() {
     var user = 'TT';
     return function () {     //通过匿名函数返回 box()局部变量
         return user;
     };
 }
 alert(box()());    //通过 box()()来直接调用匿名函数返回值
 var b = box();
 alert(b());     //另一种调用匿名函数返回值
使用闭包有一个优点, 也是它的缺点: 就是可以把局部变量驻留在内存中, 可以避免使用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,所以推荐使用私有的,封装的局部变量)。
//1. 通过全局变量来累加
 var age = 100;         //全局变量
 function box() {
     age ++;     //模块级可以调用全局变量,进行累加
 }
 box();       //执行函数,累加
 alert(age);   //输出全局变量
//2. 通过局部变量无法实现累加
 function box() {
     var age = 100;   //局部变量
     age ++;     //累加
     return age;
 }
 alert(box());   //
 alert(box());   //101,无法实现,因为又被初始化了
//3. 通过闭包可以实现局部变量的累加
 function box() {
     var age = 100;
     return function () {
         age ++;
         return age;   //返回age,实现局部变量的驻留
     }
 }
 var b = box();    //获得函数
 alert(b());     //调用匿名函数
 alert(b());     //第二次调用匿名函数,实现累加
PS:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。
问题:作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值。
代码如下:
//循环里包含匿名函数
function box() { var arr = []; for (var i = 0; i < 5; i++) { arr[i] = function () { return i; }; } return arr; } var b = box(); //得到函数数组 alert(b.length); //得到函数集合长度 for (var i = 0; i < b.length; i++) { alert(b[i]()); //输出每个函数的值,都是最后一个值 5 }
分析:上面的例子输出的结果都是 5,也就是循环后得到的最大的 i 值。因为 b[i]调用的是匿名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,i 早已变成 5,所以最终的结果就是 5 个 5。
//改 1: 自我执行匿名函数
 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 < b.length; i++) {
     alert(b[i]);        //这里返回的是数组,直接打印即可
 }
改 1 中,我们让匿名函数进行自我执行,导致最终返回给 a[i]的是数组而不是函数了。最终 b[0]-b[4]中保留了 0,1,2,3,4 的值。
//改 2: 匿名函数下再做个匿名函数
(原理同前面通过闭包实现局部变量的累加类似,闭包可以使变量驻留在内存中)
//因为每次调用外层函数时的参数不同。每次被调用的时候,它(被返回的嵌套函数)创建的作用域也有些许不同。
//也就是说,对于外层函数的每次调用,都会在作用域链中产生一个不同的调用对象。(作用域链的知识见本系列2)
 function box() {
     var arr = [];
     for (var i = 0; i < 5; i++) {
         arr[i] = (function (num) {
             return function () {     //直接返回值,改 2 变成返回函数
                 return num;       //原理和改 1 一样
             }
         })(i);
     }
 return arr;
 }
 var b = box();
 for (var i = 0; i < b.length; i++) {
     alert(b[i]());   //这里通过 b[i]()函数调用即可
 }
改 1 和改 2 中,我们通过匿名函数自我执行,立即把结果赋值给 a[i]。每一个 i,是调用方通过按值传递的,所以最终返回的都是指定的递增的 i。而不是box()函数里的 i。
关于 this 对象
在闭包中使用 this 对象也可能会导致一些问题,this 对象是在运行时基于函数的执行环境绑定的,如果 this 在全局范围就是 window,如果在对象内部就指向这个对象。而闭包却在运行时指向 window 的,因为闭包并不属于这个对象的属性或方法。
代码示例:
 var user = 'The Window';
 var obj = {
     user : 'The Object',
     getUserFunction : function () {
         return function () {   //闭包不属于 obj, 里面的 this 指向 window
             return this.user;
         };
     }
 };
 alert(obj.getUserFunction()());   //The window
//1. 可以强制指向某个对象
alert(obj.getUserFunction().call(obj)); //The Object
//2. 也可以从上一个作用域中得到对象
getUserFunction : function () {
var that = this; //从对象的方法里得对象
return function () {
return that.user;
};
}
内存泄漏
由于 IE 的 JScript 对象和 DOM 对象使用不同的垃圾收集方式, 因此闭包在 IE 中会导致一些问题。 就是内存泄漏的问题, 也就是无法销毁驻留在内存中的元素。
代码示例:
 function box() {
     var oDiv = document.getElementById('oDiv');   //oDiv 用完之后一直驻留在内存
     oDiv.onclick = function () {
         alert(oDiv.innerHTML);   //这里用 oDiv 导致内存泄漏
     };
 }
 box();
 //那么在最后应该将 oDiv 解除引用来避免内存泄漏。
 //修正:
 function box() {
     var oDiv = document.getElementById('oDiv');
     var text = oDiv.innerHTML;
     oDiv.onclick = function () {
         alert(text);
     };
     oDiv = null;   //解除引用
 }
PS:如果并没有使用解除引用,那么需要等到浏览器关闭才得以释放。
模仿块级作用域
JavaScript 没有块级作用域的概念。
代码示例1:
function box(count) {
for (var i=0; i<count; i++) {}
alert(i); // i 不会因为离开了 for 块就失效
}
box(2);
代码示例2:
function box(count) {
for (var i=0; i<count; i++) {}
var i; //就算重新声明,也不会覆盖前面的值(但是如果初始化,会执行这个值)
alert(i);
}
box(2);
以上两个例子,说明 JavaScript 没有块级语句的作用域,if () {} for () {}等没有作用域,如果有,出了这个范围 i 就应该被销毁了。就算重新声明同一个变量也不会改变它的值。JavaScript 不会提醒你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见(如果初始化了,当然还会执行的)。使用模仿块级作用域可避免这个问题。
//模仿块级作用域(私有作用域)
//使用块级作用域(私有作用域)改写
 function box(count) {
     (function () {
         for (var i = 0; i<count; i++) {}
     })();
     alert(i);   //报错,无法访问
 }
 box(2);
使用了块级作用域(私有作用域)后,匿名函数中定义的任何变量,都会在执行结束时被销毁。 这种技术经常在全局作用域中被用在函数外部, 从而限制向全局作用域中添加过多的变量和函数。 一般来说, 我们都应该尽可能少向全局作用域中添加变量和函数。 在大型项目中,多人开发的时候,过多的全局变量和函数很容易导致命名冲突,引起灾难性的后果。 如果采用块级作用域(私有作用域),每个开发者既可以使用自己的变量,又不必担心搞乱全局作用域。
(function () {
var box = [1,2,3,4];
alert(box); //box 出来就不认识了
})();
在全局作用域中使用块级作用域可以减少闭包占用的内存问题, 因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
私有变量
JavaScript 没有私有属性的概念;所有的对象属性都是公有的。不过,却有一个私有变量的概念。 任何在函数中定义的变量, 都可以认为是私有变量, 因为不能在函数的外部访问这些变量。
function box() {
var age = 100; //私有变量,外部无法访问
}
而通过函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。 而利用这一点,可以创建用于访问私有变量的公有方法。代码如下:
 function Box() {
     var age = 100;           //私有变量
     function run() {          //私有函数
         return '运行中...';
     }
     this.get = function () {   //对外公共的特权方法
         return age + run();
     };
 }
 var box = new Box();
 alert(box.get());    
可以通过构造方法传参来访问私有变量。代码如下:
 function Person(value) {
     var user = value;     //这句可以省略
     this.getUser = function () {
         return user;
     };
     this.setUser = function (value) {
         user = value;
     };
 }
但是对象的方法, 在多次调用的时候, 会多次创建。 可以使用静态私有变量来避免这个问题。
静态私有变量(类似按引用传递)
通过块级作用域(私有作用域)中定义私有变量或函数, 同样可以创建对外公共的特权方法。
代码示例:
 (function () {
          var user = ''                                  //私有变量
          //function Box() {}             
          Box = function (value) {            //全局,构造函数
                    user = value;
          };
          Box.prototype.getUser = function () {
                    return user;
          };
          Box.prototype.setUser = function (value) {
                    user = value;
          }
 })();
 var box = new Box('TT');                   //第一次实例化
 alert(box.getUser());                          //TT
 var box2 = new Box('CC');                 //第二次实例化
 alert(box.getUser());                          //CC
 box2.setUser('OO');
 alert(box.getUser());  //OO,用box2设置,用box调用说明实现共享
上面的对象声明, 采用的是 Box = function () {} 而不是 function Box() {} (就像匿名函数里面的function run(){...}方法一样,而Box因为没有var关键字,所以是全局的!)因为如果用后面这种,就变成私有函数了,无法在全局访问到了,所以使用了前面这种。
使用了 prototype 导致方法共享了,而 user 也就变成静态属性了。(所谓静态属性,即共享于不同对象中的属性)。
模块模式
之前采用的都是构造函数的方式来创建私有变量和特权方法。 那么对象字面量方式就采用模块模式来创建。
代码示例:
(第一次实例化,无法第二次实例化,那么就是单例)
var box = { //字面量对象,也是单例对象
age : 100, //这是公有属性,将要改成私有
run : function () { //这时公有函数,将要改成私有
return '运行中...';
};
};
代码示例:
私有化变量和函数:
 var box = function () {
          var user = 'TT';                                      //私有变量
          function run() {                                      //私有函数
                    return '运行中...';  
          }
          return {
                    publicGo : function () {               //对外公共接口的特权方法
                             return user + run();
                    }
          };
 }();
 alert(box.publicGo());
上面的直接返回对象的例子,也可以这么写:
 var box = function () {
          var user = 'TT';                                      //私有变量
          function run() {                                      //私有函数
                    return '运行中...';  
          }
          var obj =  {
                    publicGo : function () {         //对外公共接口的特权方法
                             return user + run();
                    }
          };
          return obj;
 }();
 alert(box.publicGo());
字面量的对象声明, 其实在设计模式中可以看作是一种单例模式, 所谓单例模式, 就是永远保持对象的一个实例。
增强的模块模式,这种模式适合返回自定义对象,也就是构造函数。
 function Desk() {}
 var box = function () {
          var user = 'TT';                                      //私有变量
          function run() {                                      //私有函数
                    return '运行中...';  
          }
          var desk = new Desk();   //实例化自定义对象
          desk.publicGo = function () {
                    return user + run();
          };
          return desk;
 }();
 alert(box.publicGo());
学习笔记,感谢李老师~
发文不易,若转载传播,请亲注明出处,谢谢!
JavaScript基础---作用域,匿名函数和闭包的更多相关文章
- Javascript设计模式之匿名函数与闭包
		匿名函数 (function () { var foo = 10; var bar = 2; console.log(foo*bar); })(); // 20 带参数的匿名函数 (function ... 
- 第一百一十节,JavaScript匿名函数和闭包
		JavaScript匿名函数和闭包 学习要点: 1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数.声明:本节内容需要有面向对象和少量设计模式基础,否则无法听懂 ... 
- JavaScript(第十五天)【匿名函数和闭包】
		学习要点: 1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数.声明:本节内容需要有面向对象和少量设计模式基础,否则无法听懂.(所需基础15章的时候已经声明 ... 
- [转]JavaScript中的匿名函数及函数的闭包
		JavaScript中的匿名函数及函数的闭包 原文地址:http://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵 ... 
- JavaScript中的匿名函数及函数的闭包(转)
		JavaScript中的匿名函数及函数的闭包 https://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵活的一种 ... 
- javascript进阶课程--第三章--匿名函数和闭包
		javascript进阶课程--第三章--匿名函数和闭包 一.总结 二.学习要点 掌握匿名函数和闭包的应用 三.匿名函数和闭包 匿名函数 没有函数名字的函数 单独的匿名函数是无法运行和调用的 可以把匿 ... 
- JavaScript笔记 第十六章 匿名函数和闭包
		1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数. 一.匿名函数 //普通函数 function getName() { return 'name'; } a ... 
- Go语言 - 函数 | 作用域 | 匿名函数 | 闭包 | 内置函数
		函数是组织好的.可重复使用的.用于执行指定任务的代码块.本文介绍了Go语言中函数的相关内容. 介绍 Go语言中支持函数.匿名函数和闭包,并且函数在Go语言中属于“一等公民”. 函数可以赋值给变量 函数 ... 
- php的匿名函数和闭包函数
		php的匿名函数和闭包函数 tags: 匿名函数 闭包函数 php闭包函数 php匿名函数 function use 引言:匿名函数和闭包函数都不是特别高深的知识,但是很多刚入门的朋友却总是很困惑,因 ... 
- js匿名函数和闭包总结
		js匿名函数和闭包总结 一.总结 一句话总结:匿名函数的最主要作用是创建闭包,闭包就是将函数内部和函数外部连接起来的一座桥梁.内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕.闭包可以用 ... 
随机推荐
- GPS 坐标距离计算
			CREATE FUNCTION [dbo].[Rad]( @d float ) RETURNS float BEGIN return @d * PI()/ 180.00; End CREATE FUN ... 
- [转]jquery Fancybox丰富的弹出层效果
			本文转自:http://www.helloweba.com/view-blog-65.html Fancybox是一款优秀的jquery插件,它能够展示丰富的弹出层效果.前面我们有文章介绍了facyb ... 
- ZooKeeper系列1:ZooKeeper的配置
			问题导读:1.zookeeper有哪些配置文件?2.zookeeper最低配置需要哪些配置项?3.zookeeper高级配置需要配置哪些项? ZooKeeper 的功能特性通过 ZooKeeper 配 ... 
- Gnome创建桌面快捷方式
			Ctrl+Alt+T打开终端 输入gnome-desktop-item-edit 桌面/ --create-new (注: 桌面/ 要改为你桌面文件夹所在路径) 配置快捷方式属性,在“命令(A)”这行 ... 
- TFSF边界条件
			待续 %1D FDTD simulation with a simple absorbing boundary condition % and a TFSF boundary between hy[] ... 
- jmeter的使用(四)
			jmeter如何调用java程序呢,下面做简单介绍.1.打开eclipse,新建项目,导入jmeter依赖的包ApacheJMeter_core.jar和ApacheJMeter_java.jar,这 ... 
- HDU 4865 Peter's Hobby --概率DP
			题意:第i天的天气会一定概率地影响第i+1天的天气,也会一定概率地影响这一天的湿度.概率在表中给出.给出n天的湿度,推测概率最大的这n天的天气. 分析:这是引自机器学习中隐马尔科夫模型的入门模型,其实 ... 
- POJ 2142 The Balance【扩展欧几里德】
			题意:有两种类型的砝码,每种的砝码质量a和b给你,现在要求称出质量为c的物品,要求a的数量x和b的数量y最小,以及x+y的值最小. 用扩展欧几里德求ax+by=c,求出ax+by=1的一组通解,求出当 ... 
- 关于comparable与comparator的用法(即自定义集合框架用法 )
			package javastudy; import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; ... 
- redis 一二事 - 设置过期时间,以文件夹形式展示key显示缓存数据
			在使用redis时,有时回存在大量数据的时候,而且分类相同,ID相同 可以使用hset来设置,这样有一个大类和一个小分类和一个value组成 但是hset不能设置过期时间 过期时间只能在set上设置 ... 
