[JavaScript高级]闭包的概念及其应用
前言:当初在学《数据库原理》的时候,就学习过闭包的概念,当时对闭包到底是什么,该怎么用却不清楚,这次通过学习JavaScript对闭包的概念和应用更加清晰了一些,下面和大家分享一下闭包的内容。
(一)闭包
1.闭包是什么
(1)闭包包含自由(未绑定到特定对象)变量;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
(2)“闭包” 一词来源于以下两者的结合:
①要执行的【代码块】(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)
②为自由变量提供绑定的计算环境【(作用域)】。
(3)在PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等语言中都能找到对闭包不同程度的支持。
2.JavaScript中闭包
在JavaScript中闭包就是【函数】
闭包就是一个封闭包裹的范围.。
函数可以限定变量的作用域。
一个变量在函数内部声明,那么在函数外部是无法访问的.。
那么这个就是一个【封闭】的范围. 广义上说就是一个闭包了。
应用场景:用闭包追加的时候,使用限制长度的队列
3.实例
①实例分析:
如果函数中又定义了函数,并将这个函数以返回值的形式返回,那么,在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语言中使用指针一样. 其基本语法简单,但是使用灵活多变,使用其灵活的语法与特征就能实现许多非常强大的功能. 在此不能阐述闭包的所有用法。
1 模拟私有成员
这个案例是JavaScript实现面向对象的基础.
var Person = function (name, age, sex) {//闭包,外部不可以修改
return {
get_Name: function () {
return name;
},
set_Name: function (value) {//非闭包,外部可以修改
name = value;
},
get_Age: function () {
return age;
},
get_Sex: function () {
return sex;
}
}
};
var p = Person("小宅", 19, "女");
alert(p.get_Name());//闭包
p.set_Name("xiaozhai");
alert(p.get_Name());//非闭包
这段代码就是一个函数,函数带有三个参数,也就是说在函数内部有三个局部变量,分别表示姓
名(name)、年龄(age)和性别(gender). 然后在返回值中,返回一个对象,该对象提供四个方法.
分别给年龄提供读写方法,给性别与年龄提供读取的方法. 这四个函数都是这个函数的子域. 因
此返回的这个对象就可以直接访问这三个变量. 但是有了读写的访问权限的限制.
2 Fibonacci(斐波那契)数列
Fibonacci(斐波那契)数列就是:1, 1, 2, 3, 5, 8, 13, …
(从第三个数开始,每一个数字等于前面两个数字相加之和)
(1)未用闭包
// 1 1 2 3
// 第n项是第n-1和n-2的和,开始的两项为1
var count1 = 0;
var fib1 = function (n) {
count1++;
if (n == 0 || n == 1) {
return 1;
}
return fib1(n - 1) + fib2(n - 2);
};
// fib1(11); // 287
// fib1(12); // 465
//fib1(13); // 753
//alert(count1);
通过输出结果我们可以看出,函数才经过过几次变化,数值就会非常大,影响性能,因此下面我们使用闭包来进行优化。
(2)闭包和未使用闭包对比
var foo1 = function() { };//未使用闭包
var foo2 = (function() {//使用闭包
// 闭包的位置
var num = "幽灵";
return function() {
};
})();
(3)使用闭包
var count2 = 0;
var fib2 = (function () {
var arr = [1, 1];//斐波那契数列
return function (n) {
count2++;
var res = arr[n];
if (res) {
return res;
} else {
arr[n] = fib2(n - 1) + fib2(n - 2);
return arr[n];
}
};
})();
/*
for (var i = 0; i < 10; i++) {
alert(fib1(i) + "," + fib2(i));
}
*/
使用闭包和没有使用闭包的次数对比:
// fib1(11); // 287次
fib2(11); // 21次
// fib1(12); // 465次
fib2(12); // 24 次
//alert(count1);
alert(count2);
这个案例一般传统的做法就是使用递归,在JavaScript中,递归使用arguments.callee()表示当前调用函数(即递归函数). 那么这么做最直接的结果是,存在一个缓存,将计算得到的结果保存在缓存中,并且实现所有的计算只计算一次,那么可以大大的提高性能。
下面的图示就是(斐波那契数列)中递归的步骤:
3 html字符串案例
在很多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代码的复制非常好用.
4 事件处理方法的追加与移除
在JavaScript中并不支持事件处理函数的追加.那么怎么来实现追加呢?
请看下面的实例:
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");
(三)闭包的优缺点
1.优点:
使得函数有【记忆】功能,提高方法的执行效率。
模拟对象 实现事件的追加和移除。
设计私有的方法和变量,防止外部修改这些属性,保护函数内部变量的安全性,增加了封装性。
2.缺点:
闭包不仅常驻内存,而且还会在使用不当的的情况下产生无效内存,造成内存的浪费