*前言:此次总结闭包,分别参考了《js高级程序设计》、廖雪峰老师的网站、还有《js忍着秘籍》,好了,废话少说,黑喂狗~~~javascript
---------------------严肃分割线-------------------*java
没错,闭包仍是要从做用域链提及,要理解闭包必须从函数第一次被调用时发生了什么入手,先看一个例子,代码:数组
function compare(value1,value2){ if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } var result = compare(5,10); //全局做用域中调用
首先定义了一个compare函数,而后又在全局做用域中调用了它。当调用compare()时,会建立一个包含(this,arguments,value1,value2)的活动对象,而全局执行环境的变量对象(this,result,compare)在compare()执行环境的做用域链中处于第二位。在这例子中,当调用compare()函数时,会为函数建立一个执行环境,其做用域链包含两个变量对象:本地活动对象和全局对象,在函数中访问一个变量时,会先从本地做用域中查找,找不到则向上查找外层函数的做用域中是否有,直到全局做用域。当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局做用域,可是闭包状况有所不一样。闭包
先看一个例子:函数
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } }; } var compareNames = createComparisonFunction("name"); var result = compareNames({name:"Jack"},{name:"Rose"}); compareNames = null; //销毁匿名函数
上面的例子中,在createComparisonFunction函数内部定义了一个匿名函数,它的做用域链包含外部函数createComparisonFunction()的活动对象和全局变量对象,因此匿名函数中能够访问createComparisonFunction()函数中的propertyName。
最重要的是:createComparisonFunction()函数在执行完毕后,它的执行环境做用域链会被销毁,其活动对象仍然会留在内存中,这就是为何上面的代码中,执行:var compare = createComparisonFunction("name")
以后,createComparisonFunction()
函数已经执行完毕,可是仍然能够在下一行代码中执行比较大小的操做。
建立的比较函数被保存在变量compareNames中,设置它等于null,解除该函数引用,垃圾回收。测试
闭包只能取得外层函数中任何变量的最后一个值。看下面例子:网站
function createFunctions(){ var arr = new Array(); for(var i=0; i<10;i++){ arr[i] = function(){ return i; }; } return arr; } var result = createFunctions(); var f1 = result[0]; var f2 = result[1]; var f3 = result[2]; //执行函数 alert(f1()); //10 alert(f2()); //10 alert(f3()); //10
这个例子中彷佛每一个函数都应该返回本身的索引值,实际上每一个匿名函数返回的都是10。由于每一个匿名函数中都保存着createFunctions()函数的活动对象,因此它们引用的是同一个变量i,函数createFunctions()返回后,变量i的值都是10,此时每一个函数都引用着保存变量i的同一个变量对象,因此每一个函数内部i的值都是10.this
注意:上述函数的调用过程:执行createFunctions()函数,而且把函数执行结果赋值给变量result,如今result是一个数组,再把result[0]赋值给f1,f1实际上表明的是内部的匿名函数,如今执行这个函数:f1(),就会获得i的值。prototype
能够经过建立另外一个匿名函数强制让闭包的行为符合预期,看下面例子:设计
function createFunctions(){ var result = new Array(); for(var i = 0; i < 10; i++){ result[i] = (function(num){ return function(){ return num; }; })(i); } return result; } var final = createFunctions(); var f1 = final[0]; alert(f1()); //0
此次没有直接把闭包赋值给数组,而是定义了一个匿名函数,并当即执行该函数的结果赋值给数组。这个匿名函数有一个参数num,也就是最终要返回的值,调用这个匿名函数时,传入了变量i,因为函数参数是按值传递的,因此会把变量i的当前值传递给num,这个匿名函数内部,又建立并返回了一个访问num的闭包,这样,最里面的匿名函数会锁住外层匿名函数传递进来的num值即当前i的值,而且返回num,这样num就会等于此时的i的值而且赋值给数组。
上述代码中很容易让人误解:闭包就是一个匿名函数,其实否则,看下面例子:
var outName = "外面的名字"; var later; function outerFunction(){ var innerName = "里面的名字"; function innerFunction(){ alert("I can see the "+outName); alert("I can see the "+innerName); } later = innerFunction; } outerFunction(); later();
上述代码中,在outerFunction()
函数中,将内部函数innerFunction()
赋值给全局变量later,执行完倒数第二步:outerFunction();
以后,执行later();
依然能够能够访问到内部变量innerName。
由于在外部函数outerFunction
中声明innerFunction()
函数时,不只声明了函数,还建立了一个闭包,该闭包不只包含函数声明,还包含了函数声明的那一时刻该做用域中的全部变量。
上述例子能够看出,闭包能够用来封装私有变量,就像java中的在对象内部封装一个private私有变量同样。
看下面例子:
function createCounter(){ var num = 0; this.getNum = function(){ return num; }; this.num = function(){ num++; }; } var counter = new createCounter(); counter.num() //调用计数器一次,使num值加1 //测试代码 alert(counter.getNum()); //1 alert(counter.num()); //undefined
上面例子中用构造函数模式建立了一个计数器函数,而后对函数进行实例化,在构造函数内部,咱们定义了一个变量num,它的可访问性只能在构造器内部,定义了一个getNum()方法,该方法只能对内部变量进行读取,但不能写入。而后又定义了方法num(),经过最后两行测试代码能够看出,能够经过存取方法getNum
获取私有变量,可是不能直接访问私有变量。
总结:私有变量的意思是,咱们若是想对num进行加减乘除的操做,只能在createCounter内部,外部只能访问内部进行逻辑操做后的值,而不能访问带有对num值进行操做的方法,这样,咱们就能够把本身的业务逻辑封装在函数内部的闭包中,只须要暴露出接口让外部获取想要获得的值就能够了,也就是说主动权彻底在你定义函数时,外部只能看和获取,而不能进行对变量值的改变的操做。
上述的目的就是建立一个用于访问私有变量的公有方法。看下面代码:
function Person(name){ this.getName = function(){ return name; }; this.setName = function(){ name = value; }; } //测试 var person = new Person("Jack"); alert(person.getName()); //Jack person.setName("Rose"); alert(person.getName()); //Rose
上面的代码在构造函数内部定义了两个方法:setName和getName,这两个方法均可以在构造函数外部访问和实用,并且都有权访问私有变量name,但在Person构造函数外部,没有任何办法访问name,因为这两个方法是在构造函数内部定义的,因此作为闭包可以经过做用域链访问name。上述在构造函数中定义特权方法有一个缺点,就是必需要使用构造函数模式来达到这个目的,这样针对每一个实例都会建立一样一组新方法。
解决办法:静态私有变量
看下面代码:
(function(){ var name = ""; Person = function(value){ name = value; }; Person.prototype.getName = function(){ return name; }; Person.prototype.setName = function(value){ name = value; }; })(); //测试函数 var person1 = new Person("Jack"); alert(person1.getName()); //Jack person1.setName("Rose"); alert(person1.getName()); //Rose var person2 = new Person("Will"); alert(person1.getName()); //Will alert(person2.getName()); //Will
上述代码中,Person构造函数与getName()和setName()方法同样,都有权访问私有变量name,name变成了一个静态的、由全部实例共享的属性。在一个实例上调用setName会影响全部的实例。或者新建一个Person实例都会赋予name属性一个新值。
上述两个方法:第二种建立静态私有变量会由于使用原型而增进代码复用,但每一个实例都没有本身的私有变量。
上面举过例子,js没有块级做用域,能够经过匿名函数当即执行把块级做用域包裹起来,这样就有了块级做用域。看下面代码:
function outputNumbers(count){ (function(){ for(var i = 0; i<count; i++){ alert(i); } })(); alert(i); //此处会致使一个错误,显示i是没有定义的。 alert(count); } //测试函数 outputNumbers(5);
上述函数中,在for循环外部插入了一个私有做用域,在匿名函数中定义的任何变量,都会在匿名函数执行结束时被销毁。所以变量i只能在for循环中使用,使用后即被销毁,所以上面的代码执行会这样:
1.执行测试函数后,会弹出5个弹窗,会显示0,1,2,3,4
2.执行完匿名函数后,i即被销毁,因此执行alert(i);
会报错。
3.能够访问变量count,由于这个匿名函数时一个闭包,它可以访问包含做用域中的全部变量。
这种技术常常在全局做用域中被用在函数外部,从而限制向全局做用域中添加过多的变量和函数,看下面代码:
(function(){ var now = new Date(); if(now.getMonth() == 0 && now.getDate() == 1){ alert("元旦快乐"); } })();
上述代码放在全局做用域中,能够用来肯定哪一天时1月1日元旦,now是匿名函数中的局部变量,不用在全局做用域中建立它。
先写这么多吧,之后再添加~~~~~