JavaScript高级之闭包的概念及其应用
主要内容:
- 什么是闭包
- 闭包使用的一般模式
- 闭包都能做些什么
本文是我的JavaScript高级这个系列中的第二篇文章. 在这个系列中,我计划分析说明
一下JavaScript中的一些常用的而又神秘的高级内容,包括:作用域链、闭包、函数调用形
式、面向对象等内容. 本文就闭包做个说明.
一说到JavaScript,就能想到闭包是个神奇的东西. 到底闭包是什么,以及怎么使用?
今天我们来分析一下!
同样,这个也属于JavaScript的高级的部分,对于JavaScript而言基础非常重要,对于
基本语法,动态语言的基本特征希望不太了解的朋友,找本书或一些系统点的资料看看. 这
样有助于对后文的理解. 当然,也可以到http://net.itcast.cn中去下载一下东西看看.
下面正式进入今天的主题.
一、何为闭包
"闭包"这个词并非是JavaScript特有的,实际上闭包是一个特有的概念. 至于概念本身
我不过多介绍,百度一下什么都有. 我主要说说JavaScript中闭包是什么.
在JavaScript中闭包就是函数
闭包就是函数,这个概念似乎感觉有点迷惑. 实际上很简单,闭包就是一个封闭包裹的 范围. 前文咱们提到过,函数可以限定变量的作用域. 一个变量在函数内部声明,那么在函
数外部是无法访问的. 那么这个就是一个封闭的范围. 广义上说就是一个闭包了!
那么这个样子其实没有什么意义. 因为没有什么特别的地方, 但是如果函数中又定义了
函数,并将这个函数以返回值的形式返回,那么,在JavaScript中"子域访问父域"的规则就
会打破了. 因为这个时候,在函数外就可以访问函数内的变量. 看下面代码:
var func = function() {
var num = 10;
return function() {
alert(num);
};
};
var foo = func();
foo();
这段代码中,函数foo是0级链,而变量num是在1级链中,这个时候,0级链的函数就访问了1级
链中的变量num,这段代码运行结果是打印出10. 这样就实现了JavaScript中的闭包.
小结一下,JavaScript中的闭包就是在函数中定义变量,然后通过返回值,将可以访问这 个变量的函数返回,这样在函数外就可以访问函数内的变量了. 这样就形成了闭包.
二、闭包的使用案例及其说明
闭包的案例非常的多. 在JavaScript中,使用闭包就像C语言中使用指针一样. 其基本语法
简单,但是使用灵活多变,使用其灵活的语法与特征就能实现许多非常强大的功能. 在此不能阐
述闭包的所有用法,但是对于刚刚接触闭包的朋友,下面的案例足够理解一段时间了.
2.1 模拟私有成员
这个案例是JavaScript实现面向对象的基础. 看下面代码
var Person = function(name, age, gender) {
return {
get_name : function() {
return name;
},
set_name : function(value) {
name = value;
},
get_age : function(){
return age;
},
get_gender : function(){
return gender;
}
};
};
这段代码就是一个函数,函数带有三个参数,也就是说在函数内部有三个局部变量,分别表示姓
名(name)、年龄(age)和性别(gender). 然后在返回值中,返回一个对象,该对象提供四个方法.
分别给年龄提供读写方法,给性别与年龄提供读取的方法. 这四个函数都是这个函数的子域. 因
此返回的这个对象就可以直接访问这三个变量. 但是有了读写的访问权限的限制.
2.2 Fibonacci数列
Fibonacci数列就是:1, 1, 2, 3, 5, 8, 13, ...
这个案例是面试题中经常考到的案例,也算是具有代表性的算法题. 看下面代码:
// 为了简单就不做n的判断处理了
var Fib = (function() {
var fibArr = [1,1];
return function( n ) {
var res = fibArr[n];
if(res) {
return res;
} else {
res = arguments.callee(n - 1) + arguments.callee(n - 2);
fibArr.push(res);
// 这里掉了一句代码
return res;
}
};
})();
这个案例一般传统的做法就是使用递归,但是递归的性能问题十分可怕,如果大家有兴趣可以 计算一下这个数列的第20项结果是多少,并统计一下这个函数递归调用了多少次. 如下面代码
var count = 0;
var fib = function(n) {
count++;
// 为了简单就不做n的判断处理了
if(n == 0 || n == 1) return 1;
return fib(n-1) + fib(n-2);
};
var res = fib(20);
alert("fib(20)的结果为:" + res + ", 函数调用了 " + count + " 次");
然后再用新方法,计算同样的结果,并统计一下次数.
var count = 0; // 为了简单就不做n的判断处理了
var Fib = (function() {
var fibArr = [1,1];
return function( n ) {
count++;
var res = fibArr[n];
if(res) {
return res;
} else {
res = arguments.callee(n - 1) + arguments.callee(n - 2);
fibArr.push(res);
return res;
}
};
})();
var res = Fib(20);
alert("Fib(20)的结果为:" + res + ", 函数调用了 " + count + " 次");
这个结果,我不在这里揭晓,请大家自己下去运行看看.
下面分析一下这段新方法的代码. 在这段代码中,绑定在Fib中的函数,实际上是后面函数运 行的返回结果. 后面这个函数有一个私有变量,是一个数组. 保存着第0项和第1项数组的值. 然后
返回一个函数. 在调用 Fib(20) 的时候就是在执行这个被返回的函数.
这个函数中,首先访问数组的第n项值,如果数组中有这个数据,就直接返回,否则实现递归
计算这个值,并将值加到数组中,最后返回计算的结果. 在JavaScript中,递归使用
arguments.callee()表示当前调用函数(即递归函数).
那么这么做最直接的结果是,存在一个缓存,将计算得到的结果保存在缓存中,并且实现所有
的计算只计算一次,那么可以大大的提高性能.
2.3 html字符串案例
这个是许多js库使用的办法,在很多js库中需要使用正则表达式处理一些数据,而如果每次执 行都在方法中保存需要处理匹配的字符串,那么会大量的消耗内存,影响性能. 因此可以将重复使
用的表达式都保存在闭包中,每次使用都是访问的这个字符串. 例如:
String.prototype.deentityify = function() {
var entity = {
lt : '<',
gt : '>'
};
return function() {
return this.replace(/&([^;]+);/g, function(a,b) {
var r = entity[b];
return typeof r === 'string' ? r : a;
});
};
}();
这段代码会将任何一个字符串中的 < 和 > 都替换成尖括号<和>,对于页面html代码的复制
非常好用.
2.4 事件处理方法的追加与移除
在JavaScript中并不支持事件处理函数的追加. 大师 Jeremy Keith 给出了一个办法:
var loadEvent = function( fn ) {
var oldFn = window.onload;
if( typeof oldFn === "function" ) {
window.onload = function() {
oldFn();
fn();
};
} else {
window.onload = fn;
}
};
不过这段代码没有办法移除已经追加的方法,那么使用闭包的缓存功能就可以轻易的实现.
var jkLoad = (function() {
var events = {};
var func = function() {
window.onload = function() {
for(var i in events) {
events[i]();
}
};
};
return {
add : function(name, fn) {
events[name] = fn;
func();
},
remove : function(name) {
delete events[name];
func();
}
};
})();
这段代码就是得到用来追加和移出load事件的对象. 如果要追加事件,可以使用
jkLoad.add("f1", function() {
// 执行代码1
});
如果要移除事件处理函数,就是用代码
jkLoad.remove("f1");
那么这个案例还可以扩展到对应以对象追加指定的事件,那么怎么实现,请大家
自己考虑吧!!!
三、小结
到此,我们已经分析了闭包是什么,以及闭包的实现一般方式,最后又分析了 几个闭包的案例. 我想大家应该对闭包有了更为深刻的理解. 那么在后面的面向对
象等高级内容中,我们将再次看到闭包的强大之处.
下面对前面问题做个解答:
第一个问题:
var func = function() {
alert("调用外面的函数");
};
var foo = function() {
func(); var func = function() {
alert("调用内部的函数");
}; func();
};
这段代码在IE下会报错,而在FF和Chrome中会没有任何效果,因为在foo中第一个函
数的调用func()就会报错,出现异常,因此后面代码不在执行. 如果需要修改,只需
要try-catch一下就好. 如:
var func = function() {
alert("调用外面的函数");
};
var foo = function() {
try {
func();
} catch ( e ) {
alert( e );
}
var func = function() {
alert("调用内部的函数");
}; func();
};
第二个问题:
if(! "a" in window) {
var a = "定义变量";
}
alert(a);
这段代码会返回 undefined.
首先,这段代码中没有函数,因此在if中定义的变量会提前,即等价于
var a;
if(! "a" in window) {
var a = "定义变量";
}
alert(a);
而 in 运算符是用来判断左边的字符串表示的属性是否是右边对象的成员. 在浏览器
中JavaScript的全局对象就是window,而直接定义的变量实际上就是全局对象的一个
属性,因此如果已经定义了变量a,那么 "a" in window 就返回true,然后取反,即
为false,所以if中的代码不会执行,就不会给a赋值,所以打印结果为 undefined.
上面代码就等价于:
var a;
if( false ) {
a = "定义变量";
}
alert(a);
JavaScript高级之闭包的概念及其应用的更多相关文章
- 《JavaScript高级程序设计》 -- 基本概念(一)
之前看过好几遍<JavaScript高级程序设计>这一书,但是始终没有完完整整的看过一遍.从现在开始我会把它完整的啃一遍,每章节都记录笔记,自己的心得,加油! 由于前三章的内容比较简单,因 ...
- JavaScript高级程序设计——闭包
前言 有很多人搞不清匿名函数和闭包这两个概念,经常混用.闭包是指有权访问另一个函数作用域中的变量的函数.匿名函数就是没有实际名字的函数. 闭包 概念 闭包,其实是一种语言特性,它是指的是程序设计语言中 ...
- javascript高级知识点——闭包
代码信息来自于http://ejohn.org/apps/learn/. 先给出一个权威的定义,函数对象可以通过作用域相互关联起来,函数体内的变量可以保存在函数的作用域内,这种特性称为闭包. 在闭包内 ...
- 读javascript高级程序设计01-基本概念、数据类型、函数
一. javascript构成 1.javascript实现由三部分组成: ECMAScript:核心语言功能 DOM:文档对象模型,提供访问和操作网页内容的方法和接口 BOM:浏览器对象模型,提供与 ...
- JavaScript 高级程序设计 01-基本概念
一.JavaScript组成 1.一个完成JavaScript是由ECMAScript.DOM.BOM三部分组成的. ECMAScript:提供核心语言功能--语法.类型.语句.关键字.保留字.操作符 ...
- JavaScript高级程序设计-(2)基础概念
for-in 语句 for-in 语句是一种迭代语句,用来枚举对象属性,语法:for (property in expression) statement实例:for(var propName in ...
- 读javascript高级程序设计00-目录
javascript高级编程读书笔记系列,也是本砖头书.感觉js是一种很好上手的语言,不过本书细细读来发现了很多之前不了解的细节,受益良多.<br/>本笔记是为了方便日后查阅,仅作学习交流 ...
- 读javascript高级程序设计-目录
javascript高级编程读书笔记系列,也是本砖头书.感觉js是一种很好上手的语言,不过本书细细读来发现了很多之前不了解的细节,受益良多.<br/>本笔记是为了方便日后查阅,仅作学习交流 ...
- JavaScript 高级程序设计 目录
为什么会写这个学习教程呢??因为一直以来,学习JavaScript都没有系统的学过,用什么学什么,所以今天开始,重新把JavaScript系统的学一遍!(本人也是菜鸟一枚,语文水平也还是小学程度,看得 ...
随机推荐
- Linux内核里的DebugFS
DebugFS,顾名思义,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据.类似的虚拟文件系统还有procfs和sysfs等,这几种虚拟文件系统都并不实际存储在硬盘上, ...
- Android自定义View之ProgressBar出场记
关于自定义View,我们前面已经有三篇文章在介绍了,如果筒子们还没阅读,建议先看一下,分别是android自定义View之钟表诞生记.android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检 ...
- javascript笔记08:javascript的if...else语句
案例代码如下: <!DOCTYPE html> <html> <body> <p>点击这个按钮,获得基于时间的问候.</p> <but ...
- iOS之layout方法-layoutSubviews、layoutIfNeeded、setNeedsLayout
下面列举下iOS layout的相关方法: layoutSubviews layoutIfNeeded setNeedsLayout setNeedsDisplay drawRect sizeThat ...
- [ lucene高级 ] 研讨如何进行Lucene的分布式应用
http://www.cnblogs.com/huangfox/archive/2010/10/15/1852206.html Lucene是个高度优化的倒转索引搜索引擎.它将倒转的索引存储在定制的文 ...
- SqlServer Change Data Capture(CDC)数据变更捕获
最近在使用SqlServer2008r2数据库做系统的时候,在某些重要的.经常涉及到修改的表上,想加上一些恢复机制,一开始想找找看看有没有类似Oracle数据库闪回那样的功能,后来发现CDC的功能可以 ...
- MVC小系列(八)【改变Areas的FindView顺序】
MVC小系列(八)[改变Areas的FindView顺序] 一般项目比较大的话,会根据模块建立Areas,这样结构清晰,也有利于路由的部署, 1 Areas下有自己的_LayOut模板,而如果希望所有 ...
- cmd命令积累
dir:展示所有目录 cd fileName:进入下一个目录 cd .. :返回上一层目录 cd\:返回根目录
- 数据库连接池php-cp介绍
php-cp(php-connect-pool)是用php扩展写的一个数据库连接池. 我们知道php开发速度快,适合创业快速迭代,但当流量大了之后,php大量的短连接给db层造成多余的消耗,而php处 ...
- iOS8 【xcode6中添加pch全局引用文件】
前沿:xcode6中去掉了pch,为了一些琐碎的头文件引用,加快了 编译速度! xcode6之前的版本建项目就自动添加了是这样的: xcode6后的版本要自己手动的添加步骤如下: 1) 2) 3) ...