js中的函数嵌套和闭包
小编已经有一段时间没有更新文章了,最近一直在考虑接下来要更新什么内容。接下来,小编会围绕以下三个方面更新文章。实际项目中遇到的问题和解决方案、Vue源码解析、代码重构、关于数据可视化。小编也会按照这个顺序,逐步的去更新。期待着一起进步。
今天就先和大家一起聊一聊我理解的闭包。在聊这个问题之前,先了解一下变量的定义域。
在js中,变量定义域有全局作用域和局部作用域之说。es6中新出现的变量声明关键字,就是为了解决部分变量作用域混乱引入的。全局作用域在这就不谈了。主要说说函数的作用域。
一、作用域
简单一点说,函数的作用域,就是函数的花括号内部,先看两个例子,或许能对这个概念理解的更好一点
function f1(){ let n = 999 console.log(n) } f1() // 999 function f2(){ let n = 999 } alert(n); // 报错
二、函数的返回值
要说闭包之前,我得先说一下函数返回值。关于函数的返回值,小编也是年初才有了一个更深层次的理解。没有返回值的函数,执行之后会返回undefined,有返回值的函数,执行之后就变成了对应的返回值。就像这样
// 没有返回值的函数 function f1(){ alert(666) } console.log(f1()) // 出现弹窗之后,在控制台输出undefind // 存在返回值 function f2(){ alert(666) return 'over' } console.log(f2()) // 出现弹窗之后,在控制台输出over。当然,可以返回字符串,也可以返回Bealon,还可以返回函数。
三、函数嵌套
在《重构——改善既有代码的设计》中,提出了js语法是允许函数内部嵌套函数的,但并不是所有的编程语言都可以的,所谓代码嵌套,就是在函数内部又有函数声明,就像这样:
function outer(){ let name = 'lilei' function inner(){ console.log(name) } }
四、闭包
前面说明了在js中的局部变量作用域的问题,在实际项目中,就是需要在函数外部,访问函数内部的变量,这个时候,按照局部变量作用域的问题。似乎是不可能的,闭包的出现,解决了这个问题。
function outer(){ let name = 'lilei' function inner(){ return name } return inner }
上面是一个典型的闭包函数,在使用这个闭包函数的时候,我们可以这样:
let g = outer() console.log(g()) // lilei
至此,已经解决了在全局访问函数内的局部变量。但是小编在回家的路上在想,为了实现这个功能,是不是不用这个麻烦,我通过这样的函数,也是可以满足需求的。
function outer(){ let name = 'lilei' return name } console.log(outer()) // lilei
确实上面的代码和通过闭包最终在控制台输出的内容是一样的,那为什么还要引入闭包呢?小编也是想了接近一周才明白的,这就好比变量->函数->类,每层往上都是逐步提升的过程,通过函数可以实现更多的逻辑,比如对数据进行处理,仅仅靠变量是不能实现的。
五、闭包的实际应用
上面小编介绍了闭包,那么在实际项目中有什么应用呢?先看下面代码:
1、隐藏内部变量名称和函数执行暂停
function outer() { let name = 1 function inner() { return name ++ } return inner } let g = outer() console.log(g()) // 2 console.log(g()) // 3 console.log(g()) // 4 console.log(g()) // 5
2、setTimeout函数传递参数
默认的setTimeout是这样的
小编也曾经这样试验过
function f1(p) { console.log(p) } setTimeout(f1(666),3000) // 并没有延时,直接输出666
要想通过延时来实现对函数传递参数,这时候,闭包的作用就显现出来了。
function f1(a) { function f2() { console.log(a); } return f2; } var fun = f1(1); setTimeout(fun,1000); // 一秒之后打印出1
3、回调
定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。就像下面这块代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试</title> </head> <body> <a href="#" id="size-12">12</a> <a href="#" id="size-20">20</a> <a href="#" id="size-30">30</a> <script type="text/javascript"> function changeSize(size){ return function(){ document.body.style.fontSize = size + 'px'; }; } var size12 = changeSize(12); var size14 = changeSize(20); var size16 = changeSize(30); document.getElementById('size-12').onclick = size12; document.getElementById('size-20').onclick = size14; document.getElementById('size-30').onclick = size16; </script> </body> </html>
4、函数防抖
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
实现的关键就在于setTimeout
这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。就像下面这样:
/* * fn [function] 需要防抖的函数 * delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //借助闭包 return function() { if(timer){ clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时 timer = setTimeOut(fn,delay) }else{ timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时 } } }
六、使用类实现类似闭包中隐藏内部变量功能
上面是一个关于闭包的实际应用,小编在晚上睡不着觉的时候,想起同样的需求,是不是也可以通过类来实现呢?最后经过一顿折腾,答案是肯定的,就像这样:
class Adder{ constructor(c){ this._c = c } increace(){ this._c ++ } decreace(){ this._c -- } get finalNum(){ return this._c } } let c = new Adder(1) c.increace() console.log(c.finalNum) // 2 c.increace() console.log(c.finalNum) // 3 c.increace() console.log(c.finalNum) // 4 c.decreace() console.log(c.finalNum) // 3
参考文章:https://www.cnblogs.com/gg-qq/p/11399152.html
https://www.cnblogs.com/pikachuworld/p/5325868.html
https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout