简单来说:闭包是一个存储了函数以及与这个函数相关的环境信息的记录。
闭包实践一:初次体验闭包
function a() { var temp = 100; function b() { console.log(++temp); } return b; } var fn = a(); // 此时fn属于全局的函数。 fn();// 101 fn();// 102
在函数a的内部return出了一个局部函数b。让函数fn接收函数a的返回值,也就是函数b。连续执行了两次fn,输出结果101,,102表示,函数fn一直引用着函数a中的局部变量temp。每次调用fn,temp都会被自增1。从此处说明了函数a一直没有被垃圾回收机制(GC)回收。以上代码还可以这样优化:
function a() { var temp = 100; return function() { console.log(++temp); } } var fn = a(); a = null; fn();// 101 fn();// 102 fn = null; // 调用完毕后要,要解除对内部匿名函数的引用,以便释放内存
闭包实践二:闭包与变量
分析下面的代码
html结构:
<ul> <li>0</li> <li>1</li> <li>2</li> </ul>
javascript结构:
var ul = document.querySelector('ul');// 为了演示方便,直接用html5的api var lis = ul.children; for(var i=0; i< lis.length; i++) { lis[i].οnclick=function(){ console.log(i); } }
当点击每个li时,输出的全都是3,在点击事件之前,for循环早已经执行完了,i的值为3。为了防止这种情况发生,for循环还可以修改成这样:
for(var i=0; i< lis.length; i++) { lis[i].οnclick=function(num){ return function(){ console.log(num); } }(i) }
由于函数是按值传递的,所以就会将变量i的当前值赋给num,而在函数内部又返回了一个访问num的闭包。这样每次i的值就保存下来了。值得一提的是在ECMAScript6中可以用严格模式下用let 来声明i。这样可以直接保存i,有关es6,以后再深入学习,示例代码如下:
javascript结构:
'use strict' let ul = document.querySelector('ul'); let lis = ul.children; for(let i=0; i< lis.length; i++) { lis[i].οnclick=function(){ console.log(i); } }
闭包实践三:对实践二的深层剖析,闭包保存的是整个变量对象,而不是某个特殊的变量。(出自 《javascript高级程序设计第三版》 181页)
/* createFunctions方法返回一个函数数组 result */ function createFunctions() { var result = new Array(); for(var i=0; i<10;i++) { result[i] = function() { return i; } } return result; } var arr = createFunctions(); // 我们拿到并输出第一个数组元素函数的返回值 var fn0 = arr[0]; var varible0 = fn0(); console.log(varible0); // 返回的是 10 // 我们拿到并输出第二个数组元素函数的返回值 var fn1 = arr[1]; var varible1 = fn1(); console.log(varible1); // 返回的是 10 // 可见闭包保存的是这个变量i对象,i的最终结果是10
我们只要对代码稍稍优化,用自执行函数来处理,就可以达到我们的预期了,如下:
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 arr = createFunctions(); // 我们拿到并输出第一个数组元素函数的返回值 var fn0 = arr[0]; var varible0 = fn0(); console.log(varible0); // 返回的是 0 // 我们拿到并输出最后一个数组元素函数的返回值 var fn9 = arr[9]; var varible9 = fn9(); console.log(varible9); // 返回的是 9
闭包实践四:闭包与this 使用不同的编程方式使用闭包,this指向不同的对象
var color = 'black'; var person = { color:"yellow", getColorFun1:function(){ return function(){ return this.color; } }, getColorFun2:function(){ var that = this; return that.color; } } console.log(person.getColorFun1()()); // 指向了 black (备注:fn()()这种写法只限于非严格模式下) console.log(person.getColorFun2()); // 指向了yellow
说明:当调用到person.getColorFun1()
的时候,在全局变量中生成一个函数function(){return this.color}
,此时的this指向是window,所以执行到person.getColorFun1()()
的时候,color为window对象下的变量color为black
而在person.getColorFun2
函数中用that保存了当前对象person,而在闭包函数里面return出去的color是person的color,所以执行完person.getColorFun2()()
的时,color是yellow。
实践四:闭包的高级应用
示例1:实现函数节流
window.onresize = throttle(function(){ var width = window.innerWidth || document.documentElement.clientWidth; console.log(width); },300); // 调节浏览器窗口,在松手后的0.3s后执行 function throttle(fn,delay) { var timer = null; return function() { clearTimeout(timer); timer = setTimeout(fn,delay); } }
示例2:实现封装对象
var Person = (function(){ var haha = 0; // 这里表示可以定义一能够使用到的参数 return function(name, age){ ++ haha; // 这里表示去使用定义到的参数,虽然在此处并没有实际意义。 this.name = name; this.age = age; }; })(); Person.prototype = { say : function(){ console.log(this.name + ' say hi'); } } var p1 = new Person('zhang san', 10); var p2 = new Person('li si', 20); console.log(p1.name); // zhang san p1.say(); // zhang san say hi p2.say(); // li si say hi
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。