闭包1(基础)
(什么是闭包?闭包的作用?闭包的缺陷?)
(闭包的几种可能的应用场景)
(闭包与内存泄漏,有关闭包的面试题)
推荐学习网站:
- http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
- 闭包
- JavaScript 闭包
- 征服 JavaScript 面试:什么是闭包
一、什么是闭包?
- Closures (闭包)就是指有权访问另一个函数作用域中的变量的函数。
- 闭包是一个函数和声明该函数的词法环境的组合。从理论角度来说,所有函数都是闭包。
- 要使用闭包,只需要简单地将一个函数定义在另一个函数内部,并将它暴露出来。要暴露一个函数,可以将它返回或者传给其他函数。
- 内部函数将能够访问到外部函数作用域中的变量,即使外部函数已经执行完毕。
二、产生一个闭包
创建闭包最常见方式,就是在一个函数内部创建另一个函数。
// closure 就是一个闭包
function func(){
var a = 1,b = 2;
function closure(){
return a+b;
}
return closure;
}
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。
三、闭包的注意事项
1、函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
// 释放对闭包的引用
add5 = null;
add10 = null;
add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。最后通过 null 释放了 add5 和 add10 对闭包的引用。
在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。
2、 闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。
function test(){
var arr = [];
for(var i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test(); // 连续打印 10 个 10
// 函数1作用域——for作用域
// 执行完for之后,在for作用域中i的值为10
for(var i = 0; i < 10; i++) { // 函数1作用域
// 我在函数1作用域中
arr[i] = function() {// 函数2作用域
// 我在函数2作用域中
return i;
};
}
for(var a = 0;a < 10;a++){
// 程序执行for的时候访问闭包function(){return i}词法作用域向上查找,
// 找到for(var i=0;i<10;i++)的词法作用域i=10,因此每次都是10
console.log(arr[a]());
}
// 毫无疑问,执行到这里的时候,i是10
// 函数2作用域中没有,向上去函数1作用域中找
console.log(i);
// 函数1作用域
对于上面的情况,如果我们改变代码如下:
function test(){
var arr = [];
for(let i = 0;i < 10;i++){ // 仅在这里作出了改动
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test(); // 打印 0 到 9
//当你换成let的时候,读取i的时候,在当前作用域(块3)中没有找到
// 向上一个作用域(块2)寻找,在块2中发现i,于是拿到值
// 块1作用域
for(let i = 0; i < 10; i++) { // 块2作用域
// 我在块2作用域中
// 在这里每次循环的块作用域都被劫持了,并且每次迭代i都被重新声明
// 即在此时,每层迭代会生成一个块作用域,并且变量i的值被定义为上次结算的值,
console.log(i); // 毫无疑问,这里的i从0依次增加到10
arr[i] = function() { // 块3作用域
// 我在块3作用域中
return i;
};
}
for(var a = 0;a < 10;a++){
//当再次访问闭包的时候访问的是劫持的块作用域,因此 i正确显示
console.log(arr[a]());
}
// 块1作用域
3、函数名与函数功能(或者称函数值)是分割开的,不要认为函数在哪里,其内部的this就指向哪里。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
return function(){
return this.name;
};
}
};
console.log(obj.getName()()); // The Window
obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(obj.getName()()); // My Object
四、闭包的应用
1、面向对象编程,实现对象数据的私有化,实例独立访问成员变量之间互不影响
-
私有变量 :任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量
-
私有变量包括函数的参数、局部变量和函数内定义的其他函数。
-
特权方法:把有权访问私有变量的公有方法称为特权方法(privileged method)。
function Animal(){
// 私有变量
var series = "哺乳动物";
function run(){
console.log("Run!!!");
}
// 特权方法
this.getSeries = function(){
return series;
};
}
- 匿名函数最大的用途是创建闭包
- 使用闭包模拟私有方法, 可以限制对代码的访问,减少全局变量的使用和污染,提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
var objEvent = objEvent || {};
(function(){
var addEvent = function(){
// some code
};
function removeEvent(){
// some code
}
objEvent.addEvent = addEvent;
objEvent.removeEvent = removeEvent;
})();
在这段代码中函数 addEvent 和 removeEvent 都是局部变量,但我们可以通过全局变量 objEvent 使用它,这就大大减少了全局变量的使用,增强了网页的安全性。
var countNumber = (function(){
var num = 0;
return function(){
return ++num;
};
})();
2、 匿名自执行函数
创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
- 匿名函数:顾名思义,就是没有方法名的函数
- 匿名函数调用方式:使用()将匿名函数括起来,然后后面再加一对小括号(包含参数列表 )
// 将匿名函数赋值给一个变量,通过变量引用进行函数调用
(function(a,b)
{
console.log('匿名函数加圆括号:'+(a+b));
}(1,2));
var noName= function(a,b){
console.log('匿名函数赋值变量:'+(a+b));
}(1,4);
3、 缓存(未整理验证)
例:设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
(1)
/*
* 闭包实现缓存
* 属性:有个键--值 --->所以可以将缓存数据存放在一个对象中
* 缓存存储方法 setCache
* 缓存的获取方法 getCache
*/
function configCache(){
var obj={};//设置一个内部的对象 用来存储缓存数据;这个属性是私有的
//对外暴露两个公共的方法
return {
setCache:function(k,v){//设置缓存
obj[k]=v;
},
getCache:function(k){//获取缓存
return obj[k];
}
};
}
var conf = configCache();
console.log(conf);
conf.setCache(1,'sdwqecwqv');
console.log(conf.getCache(1));//sdwqecwqv
// 注意下面这种情况,两次configCache()会产生不同的执行环境
configCache().setCache(1,'sdwqecwqv');
console.log(configCache().getCache(1));//undefined
(2)
function doSometingToGetData () {
//可以在这里向后台取数据,也可以是其他操作,目的就是获取你需要缓存的数据并返回
return data;
}
var a = (function b () {
var _cache = {};
return {
setCache: function (name) {
_cache[name] = doSometingToGetData(name);
},
getCache: function (name) {
return _cache[name];
}
}
});
var c = function () {
a.setCache('daihere');
var data = a.getCache('daihere');
}
(3)
var a = (function b () {
var _cache = {};
return {
doSometing: function (name) {
if (name in cache) {
var data = _cache[name];//获取缓存数据
doSometingByData(data);
}else {
_cache[name] = doSometingToGetData(name);
}
},
};
})();
function doSometingToGetData () {
//可以在这里向后台取数据,也可以是其他操作,目的就是获取你需要缓存的数据并返回
return data;
}
4、实现封装
闭包可以封装形成‘私有变量‘,如:实现计算乘积:
const mult = function() {
var a = 1;
for (let i = 0; i < arguments.length; i++) {
a = a * arguments[i];
}
return a;
}
mult(1,2,3,4) // 24
五、闭包的缺陷
-
局部变量本来在函数退出时被销毁,然而闭包中不是这样,局部变量生命周期被延长,闭包将使这些数据无法及时销毁,常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
-
如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
六、面试题
例1:
function fun(n,o){
console.log(o);
return {
fun: function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3); // ?,?,?,?
var b = fun(0).fun(1).fun(2).fun(3); // ?,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3); // ?,?,?,?
a: undefined,0,0,0
b: undefined, 0, 1, 2
c: undefined, 0,1,1
例2:
for (var i = 1; i <= 5; i++) {
setTimeout( function timer() {
console.log(i);
}, 1000 );
}
上面代码输出什么?如何改动使它依次输出1、2、3、4、5
// 上面代码返回6、6、6、6、6
// 方法一
for (let i = 1; i <= 5; i++) {
setTimeout( function timer() {
console.log(i);
}, 1000 );
}
// 方法二
for (var i = 1; i <= 5; i++) {
(function(i){
setTimeout( function timer() {
console.log(i);
}, 1000 );
})(i);
}