闭包
由于多次面试被问到有关闭包都没答出来,都是一细问就露馅的那种,就看了一下周啸天老师讲的闭包,真的讲的很好,有需要可以去看一下,也可以先看看我做的笔记还有哪一块不会的就可以对应的去看一下视频
首先需要先把作用域这个基础知识给了解清楚,可以先通过题看看,自己是否还有哪个知识点是不了解的
作用域
- 全局变量与私有变量
//先变量提升,再自上而下执行
var a = 12,
b = 13,
c = 14;//全局变量
function fn(a){
//形成私有作用域,形参赋值 a = 12;变量提升 var b
console.log(a,b,c);//12,undefined,14
var b = c = a = 20;
//var b = 20; c = 20; a = 20
console.log(a,b,c);//20,20,20
}
fn(a) //fn(12)
console.log(a,b,c)//12,13,20
-
作用域链:如果这个变量不是私有的,则一级一级往上作用域找直至全局作用域
-
在私有作用域中,只有以下两种情况是私有变量,其余的都需要基于作用域链机制向上查找
- 声明过的变量(带var/function)
- 形参
练习
var ary = [12,23]
function fn(ary){
console.log(ary) //[12,23]
ary[0] = 100
ary = [100]
ary[0] = 0
console.log(ary)//[0]
}
fn(ary)
console.log(ary)//[100,23]
上级作用域的查找
当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和它在哪儿执行的没有关系,与它在哪创建的有关系
var n = 10;
function fn(){
var n = 20;
function f(){
n++
console.log(n)
}
f()
return f
}
var x = fn()
x()//22
x()//23
console.log(n)//10
堆栈内存释放
-
堆内存是存储引用数据类型值(对象:键值对 函数:代码字符串)
- 堆内存释放:让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,这个浏览器会在空闲的时候把它释放掉)
-
栈内存是提供JS代码执行的环境和存储基本类型值
- 栈内存释放:一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值都会释放掉),但是也有特殊不销毁情况
- 当函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了。此时栈内存不能释放(一旦释放,外面找不到原有的内容了)
- 全局栈内存只有在页面关闭的时候才会被释放掉
- 如果当前栈内存没有被释放,那么之前在栈内存中存储的基本值也不会被释放,能够一直保存下来
- 栈内存释放:一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值都会释放掉),但是也有特殊不销毁情况
var i = 1
function fn(i){
return function(n){
console.log(n + (++i))
}
}
var f = fn(2)
f(3) // 3+3=6
fn(5)(6) //6+6=12
fn(7)(8) //8+8=16
f(4) //4+4=8
闭包应用
- 函数执行形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为“闭包”;市面上的开发者认为的闭包是:形成一个不销毁的私有作用域(私有栈内存)才是闭包
//==>闭包:柯里化函数
function fn(){
return function(){
}
}
var f = fn()
//==>闭包:惰性函数
var utils = (function(){
return{
}
})()
闭包项目实战应用
- 真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)
-
闭包具有保护作用:保护私有变量不受外界的干扰
-
在真实项目中,尤其是团队写作开发的时候,应当尽可能的减少全局变量的使用,以防止相互之间的冲突(全局变量污染),那么此时我们完全可以把自己的这一部分内容封装到一个闭包中,让全局变量转换为私有变量
-
(function(){ var n = 12; function fn(){ } //... })()
-
不仅如此,我们封装类库插件的时候,也会把自己的程序都存放到闭包中保护起来,防止和用户的程序冲突,但是我们又需要暴露一些方法给客户使用,此时处理方法常常有以下两种:
-
JQ这种方式:把需要暴露的方法抛到全局
(function(){ function jQuery(){ //... } //... window.jQuery = window.$ = jQuery//把需要暴露供外面使用的方法,通过给window设置属性的方式暴露出去 })()
-
zepto这种方式:基于return把需要供外面使用的方法暴露出去
var zepto = (function(){ //... return { } })() //单例模式
-
-
-
闭包具有保存作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用
for(var i=0;i<6;i++){ setTimeout(function(){ //异步 console.log(i) //6 6 6 6 6 6 i不是私用变量,往上级作用域查找,此时循环早已结束,window中的i等于6 },1000) } //在传统的ES5中,判断与循环不会产生任何的作用域 //解决方案1:闭包 for(var i=0;i<6;i++){ setTimeout((function(n){ var i = n //n.i都为私有变量 return console.log(i) //0 1 2 3 4 5 })(i),1000) //让自执行函数执行,把执行的返回值赋值 } //同一本质不同写法 for(var i=0;i<6;i++){ (function(i){ setTimeout(function(){ console.log(i) },1000) })(i) } //循环6次,形成6个不销毁的私有作用域(自执行函数执行),而每一个不销毁的栈内存中都存储了一个私有变量i,而这个这个值分别是每一次执行传递进来的全局i的值,当执行setTimeout函数时,遇到变量i,向它自己的上级作用域查找,查找到的值分别是0/1/2/3/4/5,达到我们想要的效果,但是很耗性能 //解决方案2:ES6解决 for(let i=0;i<6;i++){ setTimeout(function(){ console.log(i) },1000) } //基于ES6中的let来创建变量,是存在块级作用域的(类似于私有作用域)
作用域:(栈内存)
-
全局作用域
-
私有作用域
-
块级作用域(一般用大括号包起来的都是块级作用域(对象的大括号不是),前提是ES6语法规范,let/const存在暂时性死区)
-