目录
1 变量作用域
- 变量作用域的概念:就是一个变量可以使用的范围
- JS中首先有一个最外层的作用域:称之为全局作用域
- JS中还可以通过函数创建出一个独立的作用域(局部),其中函数可以嵌套,所以作用域也可以嵌套
var gender="男"; //全局变量
function fn(){
console.log(age); //因为age是在fn作用域内声明的
//age:undefined:既然有值就是可以访问
console.log(height);//height不是在该作用域内部声明的,所以不能访问
//-->2级作用域
return function(){
//-->3级作用域
var height=180;
}
var age=5;
}
//注意:变量的声明和赋值是在两个不同时期的
function fn(){
console.log(age); //undefined
var age=18;
console.log(age); //18
}
说明:看是否能被访问,看是否能获取到值
- undefined:表示声明了但是未赋值,能输出这个值,说明是能访问到该变量,否则控制台直接报错
- fn函数执行的时候,首先找到函数内部所有的变量、函数声明,把他们放在作用域中,给变量一个初始值:undefined -->变量可以访问
- 逐条执行代码,在执行代码的过程中,如果有赋值语句,对变量进行赋值(var声明变量)
2 作用域链
问题引入:由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?
我们把这个变量的查找过程称之为变量的作用域链。简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)
- 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
- 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
- 再查找上级函数的上级函数,直到全局作用域为止
- 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
<script>
function fn(callback){
var age=18;
callback()
}
fn(function(){
console.log(age); //报错
//分析:age变量:
//1、查找当前作用域:并没有
//2、查找上一级作用域:全局作用域
//-->难点:看上一级作用域,不是看函数在哪里调用,而是看函数在哪里编写
//-->因为这种特别,我们通常会把作用域说成是:词法作用域
})
</script>
3 认识闭包
例:1
<script> function fn(){ var a=5; return function(){ a++; console.log(a); //a变量肯定是可以访问的 } } var f1=fn(); //f1指向匿名函数 f1(); //6 f1(); //7 f1(); //8 </script>
代码执行到var f1=fn()行时,fn函数执行完毕,返回匿名函数
- 一般认为函数执行完毕,变量就会释放,但是此时由于js引擎发现匿名函数要使用a变量,所以a变量并不能得到释放,而是把a变量放在匿名函数可以访问到的地方去了
- a变量存在于f1函数可以访问到的地方,当然此时a变量只能被f1函数访问
例:2
//返回值为 值 function q1() { var a = {}; return a; } var r1 = q1(); var r2 = q1(); console.log(r1 == r2);//false //返回值为 函数 function q2() { var a = {} return function () { return a; } } var t3 = q2();//创建一个新的a对象,把a对象放在t3可以访问到的位置 var o5 = t3(); //返回值a就是那个a var w3 = q2();//创建了一个新的a对象,把新的a对象放在w3可以访问到的位置 var o8 = w3();//此时获取到的是一个新的a对象 console.log(o5 == o8); //false
个人理解:声明多个变量指向外层函数,其多个变量互不干扰,相互独立;声明一个变量t1指向外层函数,一直用这个变量t1改变内层函数(闭包函数),如例1所示,会有所变化影响
4 经典面试题
例:点击事件:当点击对应的div内容时,弹出对应的序号
- 解决前:效果是点击任意一个div内容,都只会弹出 数字5
- 解释:闭包问题,执行for结束后,i变成了数字5
- 闭包问题产生原因:函数执行完毕后,作用域中保留了最新的i变量的值
<body> <div>111</div> <div>222</div> <div>333</div> <div>444</div> <div>555</div> </body> <script> //解决前 // var divs=document.getElementsByTagName("div"); // for (var i = 0; i < divs.length; i++) { // const element = divs[i]; // element.onclick=function(){ // //i是来自于全局作用域 // alert(i) // } // } //执行完for循环之后,i的值已经变成了5 } </script>
解决后:能实现问题描述
//解决后 var divs = document.getElementsByTagName("div"); for (var i = 0; i < divs.length; i++) { const element = divs[i]; //闭包的解决方案:为每个元素创建一个独立的闭包,闭包捕获了当前的索引i element.onclick = (function (currentIndex) { return function () { // currentIndex是捕获的当前循环索引的副本,它是通过闭包传递给内部函数的 alert(currentIndex + 1) } })(i);
5 闭包的应用
- 模块化
- 防止变量被破坏
场景:在KTV里消费,规定只有消费满1000才能离开;未达到要求,则需要继续购物。
//模块化思想:也是一种设计模式 var ktv=(function KTV(){ //为了保护leastPrice变量,将它放在函数内部 var leastPrice=1000; var total=0; return { //购物 buy:function(price){ total+=price; }, //结账 pay:function(){ if(total<leastPrice){ console.log('请继续购物'); }else{ console.log('欢迎下次光临'); } } } })()
增加情景:来了一个老板的朋友要来唱K,不强制必须消费到1k,则说明老板需要能够修改最低消费金额,但是处于安全考虑,并不能让老板直接去修改leastPrice,或者说不能把leastPrice作为全局变量
修改return返回的内容:添加下面的函数
editLeast:function(id,price){ if(id===888){ leastPrice=price; console.log("现在最低消费金额为:",leastPrice); }else{ console.log('权限不足'); } }
6 闭包内存释放
<script>
function f1() {
var a = 5;
return function () {
a++;
console.log(a);
}
}
var q1 = f1();
//要想释放q1里面保存的a,只能通过释放q1
q1 = null; //q1=undefined //因为对象类型都是引用类型
</script>
7 闭包的优势
标签:闭包,function,函数,作用域,JS,var,变量,07 From: https://blog.csdn.net/m0_68467925/article/details/139309996
- 实现私有变量和数据封装:
- 闭包允许我们创建私有变量,这些变量只能在内部函数中访问和修改,外部无法直接访问。这提供了一种封装机制,可以隐藏数据的细节,从而提高代码的安全性。
- 通过这种方式,我们可以将相关的数据和函数封装在一个闭包中,形成一个模块,这有助于减少全局变量的使用,防止全局变量污染。
- 保持数据的持久性:
- 闭包使得内部函数可以持续访问外部函数的变量,即使外部函数已经执行完毕。这对于需要保持数据状态或延长变量生命周期的场景非常有用。
- 通过闭包,我们可以保存函数的内部状态,即使在函数执行结束后,状态仍然被保留。
- 创建函数工厂和动态函数:
- 闭包可以动态生成函数,每个函数都有自己的独立作用域和状态。这使得我们可以根据不同的参数生成不同的函数,实现函数工厂的模式。
- 逻辑连续性和避免额外逻辑:
- 当闭包作为另一个函数调用的参数时,可以避免脱离当前逻辑而单独编写额外逻辑。这有助于保持代码的清晰和逻辑的连续性。
- 方便调用上下文的局部变量:
- 闭包允许内部函数访问外部函数的变量,这使得我们可以更方便地调用和操作上下文中的局部变量。
- 加强封装性,达到对变量的保护作用:
- 通过将变量和函数封装在闭包中,我们可以增强代码的封装性,从而达到对变量的保护作用。这有助于防止外部代码对内部数据的非法访问和修改。
然而,尽管闭包具有许多优势,但也需要注意其潜在的缺点,如内存消耗、性能问题、错误处理困难和对象状态共享等。因此,在使用闭包时需要根据具体的场景和需求来权衡其优缺点,并合理使用闭包以发挥其最大的优势。