开场白
对于js来说,闭包可谓时重点和难点,前端面试应该是必问的题,对闭包的理解程度就可以看的出一个前端开发的功底。网上对闭包的文章比比皆是,但是对于初学者来说不是很友好。今天我就考虑一下初学者,毕竟我还算是吧,简单通俗的讲一下js闭包。
准备工作
要修炼js的闭包功夫,还需要一点其他功力作为功底的。
- 功底一:js的垃圾回收知识
- 功底二:js的函数执行过程
- 功底三:js的全局变量和局部变量
大概就明白这三点就可以理解闭包了吧。对这三点也就理解个基本 不用过深的理解比较复杂的情况,如js的预处理机制,js的堆栈内存,js的变量提升什么的。
现在我们就简单学习一个上面三套功法。都是之学招式不学精髓。
看了一下功底三最为好学。一句话 函数内生成的变量叫局部变量 函数外生成的变量叫全局变量 话不严谨应该好理解,其实后面还有两句,函数内可以访问全局变量,函数外访问不了局部变量 ;太长了不太好记。用例子来说吧。
var a = 'AAAA';
function fn() {
var b = 'BBB';
console.log(a) //打印AAA
}
fn();
console.log(b) // b is not defined
上面例子就不解释了。。。
再看一下js的垃圾回收机制:也是一句话 局部变量在函数执行完成之后自动回收 就拿上面的函数来说,当fn执行完成之后函数内创建的变量会自动回收
最麻烦的功底二来了,关于函数的执行过程,基础的就是函数开始不执行,调用之后从上到下依次执行,编译错误和return就结束,但是复杂的就是有函数的 有return的。举个例子吧
function fn() {
var b = 'BBB';
return function () {
console.log("CCCC")
}
}
fn();
咱们来分步执行一下
- 代码开始了走到fn(){} 这个函数,没有执行语句跳过它。
- 走到fn() 开始执行fn这个函数 最终返会了一个函数。结束
- 函数结束 局部变量 b 被回收
再变一下型 往后加个剧本
function fn() {
var b = 'BBB';
return function () {
console.log("CCCC")
}
}
var c = fn();
c();
接上面的 返回一个函数 functio(){console.log('CCC')} 赋值给 c这个变量;(加一句:b被回收) 接着执行c() 这个函数打印出CCC(加一句 如上面return的函数内定义变量,也会被回收)
闭包开讲
准备工作完成之后一个普通函数的经历过程应该可以看的懂了吧。
那就看一个例子一步一步执行一下
/*1*/ function fn() {
/*2*/ var b = 'BBB';
/*3*/ return function () {
/*4*/ console.log(b)
/*5*/ }
/*6*/ }
/*7*/ var c = fn();
/*8*/ c();
/*9*/ c();
/*10*/ c();
函数有点长给标一个行号 ;长函数不要着急 一步一步来。
- 1-6行定义了一个函数不执行
- 第7行返回了一个函数
function () { console.log(b) } 赋值个变量c c就是该函数了
- 按照上面的垃圾回收 函数内的b变量被回收
- 第8行执行函数c 打印b,因为b被销毁了 所以应该是 b is not defined 结束 9行10行就不执行了
代码复制运行 垃圾小编 不对! 看来js处理和我们想的不一样。
那就一起来分析一下吧? 结果显示 打印了三个 BBB,说明程序中没有出错,也就说b变量没有被销毁,再说一下就是外面可以访问内部函数定义的变量;这就生成了闭包。闭包最核心的也就是打破了js的垃圾回收机制,让局部变量不被销毁。曾看到一篇文章把闭包比喻成一个背包,可以把函数的变量放进去,当它被返回时就一起带走了。
那咱们再走一边上面程序,加一句话
/*1*/ function fn() {
/*2*/ var b = 'BBB';
/*3*/ return function () {
/*4*/ b = b+'B'
/*5*/ console.log(b)
/*6*/ }
/*7*/ }
/*8*/ var c = fn();
/*9*/ c();
/*10*/ c();
/*11*/ c();
- 1-7行定义一个函数
- 第8行返回了一个函数
function () { b = b+'B' console.log(b) } 赋值个变量c c就是该函数了
- 因为返回的函数 用到fn这个函数内生成的b变量。所以就打开背包把 b='BBB',给背走了,就成了以下函数
var b = 'BBB' function () { b = b+'B' console.log(b) }
- 第九行执行c函数两步① b 的值改为为'BBB'+'B' 为BBBB ② 打印'BBBB'
- 第10行再执行c函数 刚才把b的值已经改为'BBBB'了 再加一个B 打印'BBBBB'
- ...
这就是一个简单的闭包例子,也是很多笔试题闭包实现数字累计的方法。
有的同学就会说 定义一个全局变量,底下函数直接给赋值就好了。这里只讲闭包不讲程序设计,为何要避免全局变量。要实现一个功能for 0-100循环不是也可以直接写100下。
知识梳理
- 闭包产生的条件:函数内部函数作为返回值,并使用函数内的局部变量
- 闭包的表现:所用的变量不被销毁,一直存在内存中 这就是闭包常说的 可以导致内存泄露 和 变量提升。这就是可以成为私有属性创建模式
应用案例
1.实现累加计数 这个例子和上面的差不多 只是有两个return 按上面的就很好理解了
function fn() {
var init = 0;
return function () {
init = init+1
return init;
}
}
var fun = fn();
var add1 = fun();
var add2 = fun();
var add3= fun();
var add4 = fun();
console.log(add1);
console.log(add2);
console.log(add3);
console.log(add4);
2.实现函数私有属性
1. function book() {
2. var page = 100;
3. return function () {
4. this.auther = 'wuy';
5. this.price = 200;
6. this._page = function () {
7. return page
8. }
9. }
10. }
11. var books = book();
12. var cssbook = new books();
13. console.log(cssbook._page()); //打印100
14. console.log(cssbook.auther); //打印wuy
简单分析一下
- 1-10行 先定义一个函数book ,函数,
- 11行 函数开始运行 执行 2-9行代码,定义一个page局部变量 返回返回值为一个函数 3-9行 返回的函数就用到了book内定义的page 闭包形成 page 一直存在内存中。
- 第12行 返回的函数 3-9行 是一个构造函数用new关键字声明成一个对象 赋值为cssbook;因此函数内部的this指向cssbook
- 第13行 打印cssbook的_page()这个方法的返回值 就是内存中的page 100 ;这就是函数的私有属性
- 第14行 打印cssbook的auther wuy