自己对闭包的一些小理解

来源:互联网 发布:什么学英文软件 编辑:程序博客网 时间:2024/05/22 10:58

在看闭包的时候,只知道闭包可以从外部读取局部变量,在一次小考核中发现自己根本不会用闭包,又赶紧恶补了一波

什么是闭包
先说下官方对闭包的解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
看到这句话估计很多人和我一样是十分懵的,不要怕下面看波代码简单理解什么是闭包

function a() {  var i = 0;  function b() { alert(++i); }  return b;}var c = a();c();

很容易看出来c()其实是指向a()下的b(),也就是说当函数a的内部函数b被函数a外的一个变量引用的时候,这就创建了一个闭包。
借用网上的一段话:  
所谓“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标 对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目 标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新 的值,和上次那次调用的是各自独立的。
是不是找到一点感觉了,再看一个复杂的

function count() {    var arr = [];    for (var i=1; i<=3; i++) {        arr.push(function () {            return i * i;        });    }    return arr;}var results = count();var f1 = results[0];var f2 = results[1];var f3 = results[2];

可能有人看见就会说 soeasy 1,4,9。但是运行下来却是16,16,16。尴尬吧。
原因在于返回的函数引用了变量i;但是并没有立即执行,等到三个函数都返回时,i值已变为4;结果就为16了。
所以返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
当然如果必须用可以用下面的方法解决

function count() {    var arr = [];    for (var i=1; i<=3; i++) {        arr.push((function (n) {            return function () {                return n * n;            }        })(i));    }    return arr;}var results = count();var f1 = results[0];var f2 = results[1];var f3 = results[2];f1(); // 1f2(); // 4f3(); // 9

方法即为创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值都不会变。

看一个稍微有难度的

      var name = "The Window";      var object = {        name : "My Object",        getNameFunc : function(){          return function(){            return this.name;          };        }      };      alert(object.getNameFunc()());//The Window      var name = "The Window";      var object = {        name : "My Object",        getNameFunc : function(){          var that = this;          return function(){            return that.name;          };        }      };      alert(object.getNameFunc()());//My Object

这里我就直接说了,第一段代码因为在object.getNameFunc()执行后只是声明要调用return function(){return this.name;};这个函数,这个函数的作用域其实是windows,而第二段代码定义var that = this;这里的this指向object,所以return function(){return that.name;};其实就是指向object.name;
当然也可以在第一段代码基础运用apply,bind等改变作用域;想了解的可以直接看下一篇。嘿嘿。
最后上一段特别的代码

// 定义数字0:var zero = function (f) {    return function (x) {        return x;    }};// 定义数字1:var one = function (f) {    return function (x) {        return f(x);    }};// 定义加法:function add(n, m) {    return function (f) {        return function (x) {            return m(f)(n(f)(x));        }    }}

这个是叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。
厉害吧,不过太饶了。
在加上一段(网上高手)的解析

首先说明,要打印多少次就是要执行这个f函数多少次
var zero = function (f) {
return function (x) {
return x;
}
};
执行(zero(function(){console.log(‘0’);}))()
执行了最外面的function(f),传入的参数是function(){console.log(‘0’);,返回了function(x),因为用了一个“创建一个匿名函数并立刻执行”的语法:,所以function(x)执行,x是空,返回x。(这里跟f毫无关系)
var one = function (f) {
return function (x) {
return f(x);
}
};
(one(function(){console.log(‘1’);}))() ,这里返回的是f(x),f是什么上面已经说了,x就是空,所以打印一次
要打印两次可以这么做
var two=function(f){
return function(x){
f(f(x));}
}
three可以这么写
var two=function(f){
return function(x){
f(f(f(x)));}
}
f无论接受什么参数都会执行console.log()
所以无论是zero,one,two,three,four,five都是两层函数嵌套,只需要执行两次,最后的返回就是要打印多少次,就执行f多少次,所以add可以这么写
function add(n,m){
return function(f){
return function(x){
return m(f)(n(f)(x));}
}
}
n,m参数会可以被嵌套在add函数里的任意函数调用,就好像是子函数继承父函数的参数一样,f还是上面的f,将最后的return分解开来看,先是n(f)(x),n就是one,two,three。。。。中的一个
假如是add(one,two),n就是one,上面说了(zero,one,two,three,four,five。。。都是两层函数嵌套,只需要执行两次,最后的返回就是要打印多少次,就执行f多少次),所以这里n是多少就打印多少次,然后m(f)(n(f)(x))将n(f)(x)看成一个整体即一个参数,同理,打印m次,所以加起来打印n+m次

不管你们懂不懂 ,反正我是挺懵的。
最后说下闭包的用途以及注意点
一.用途
一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
二.注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。