学习JavaScript中的闭包
一、闭包
关于什么是闭包,官方及不同的书籍中都给出了表达不太一样的概念。在自己梳理概念的过程中,也看了很多前辈的学习心得,就我个人而言,我觉得最好理解的是js高程里给的概念:闭包是指有权访问另一个函数作用域中的变量的函数。
在JavaScript中,我们知道js作用域中访问变量的权利是由内向外的,也就是内部作用域可以从当前作用域下开始一层一层向外获得外层作用域的变量,但是反之则不能。但是出于种种原因,我们有时候也希望能访问函数内部的变量,闭包就是解决这一需求的,它的本质就是在一个函数内部再定义另一个函数。
基于闭包的概念,就可以理解闭包的作用了:
1.可以在函数的外部访问到函数内部的变量
2.让这些变量始终保存在内存中,不会被垃圾收集机制清除
function fun(){ var num=0; return num+=1; } var fn1=fun() console.log(fn1)//1 console.log(fn1)//1
这个例子中两次打印结果都是1,因为变量num属于局部变量,每次fun执行完之后它就把属于自己的变量num连同自己一起销毁。
引入闭包:
function fun(){ var num=0; return function(){ return num+=1; } } var fn1=fun() console.log(fn1())//1 console.log(fn1())//2
这里,匿名函数作为fun的返回值赋值给了fn1,且匿名函数内部引用了fn里的变量num,所以变量num无法被销毁,会一直保存在内存中直到fn1被销毁。
再看一个之前我踩过坑的例子
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; console.log(object.getNameFunc()());//The window--this指向window,不存在闭包
这一段代码输出The window。因为getNameFunc的返回值是一个匿名函数,匿名函数的执行环境具有全局性,它的this一般指向window,在上面这段代码中匿名函数中的this指向的就是window。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var _this = this; return function(){ return _this.name; }; } }; console.log(object.getNameFunc()());//My Object--this指向object,存在闭包
上面这段代码的输出是My Object。我们知道,如果想要匿名函数的this能够执行当前的Object对象,就要更改this。在这段代码中,我们在匿名函数外添加了一行var _this = this用于更改this的指向。这里的getNameFunc相当于一个闭包。
这个例子也可以体现出闭包的作用:可以延长变量的使用范围
二、闭包的使用场景
1.setTimeout
原生的setTimeout传递的第一个参数不能带参数,可以通过闭包实现传参。
function fun(param){ return function(){ console.log(param) } } var f1 = fun(1) setTimeout(f1,1000)//1秒后输出1
2.回调
定义行为,把它关联到某个用户事件上。代码通常作为一个回调(事件触发时调用的函数)绑定到事件
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>position</title> <style type="text/css"> body{ margin: 0; margin-left: 100px; } #div1{ height: 150px; width: 200px; top: 50px; left: 100px; border: #15179e medium double; } </style> </head> <body> <p>1</p> <hr /> <div id="div1"></div> <hr /> <p>1</p><p>1</p><p>1</p><p>1</p><p>1</p> <p> <button id="position-static">static</button> <button id="position-relative">relative</button> <button id="position-absolute">absolute</button> <button id="position-fixed">fixed</button> </p> <script> function changePosition(str){ return function(){ document.getElementById("div1").style.position=str } } var set1 = changePosition("static") var set2 = changePosition("relative") var set3 = changePosition("absolute") var set4 = changePosition("fixed") document.getElementById("position-static").onclick= set1 document.getElementById("position-relative").onclick= set2 document.getElementById("position-absolute").onclick= set3 document.getElementById("position-fixed").onclick= set4 </script> </body> </html>
3.函数防抖
防抖是指在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。(ps:节流是指连续触发事件但是在 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) // 进入该分支说明当前并没有在计时,那么就开始一个计时 } } }
4.封装变量
//用闭包定义能访问私有函数和私有变量的公有函数。 function counter(){ var privateCounter = 0; //私有变量 function change(val){ privateCounter += val; } return { increment:function(){ //三个闭包共享一个词法环境 change(1); return privateCounter; }, decrement:function(){ change(-1); return privateCounter; }, value:function(){ return privateCounter; } }; }; var res = counter(); console.log(res.value())//0 console.log(res.increment())//1 console.log(res.increment())//2 console.log(res.decrement())//1