堆栈内存
栈内存:作用域
- 提供一个供js代码自上而下执行的环境(代码都是在栈内存中执行)
- 存储基本数据类型值
- 释放:当栈内存被销毁,存储的那些基本值也跟着被销毁了(一般函数执行完成所形成的私有作用域会自动释放,除非某些内容被栈内存以外的变量占用了此时栈内存不能释放)全局栈内存只有在页面关闭的时候被释放
堆内存:引用值对应的空间
- 存储引用类型值(对象:键值对 函数:代码字符串)
- 当前堆内存释放销毁,那么这个引用值彻底没了
- 释放:当对内存没有被任何的变量或其他东西所占用,浏览器会在空闲的时候自主进行内存回收,把所有不被占用堆的内存销毁掉。
比如我让ary1=null,null是空对象指针,让原始变量谁都不指,原有被占用的对内存就没有被东西占用了,浏览器在空闲时候就把该堆内存销毁了。
举例:
var a=10经历三个过程,声明变量a,没有赋值默认是undefined;在当前作用域开辟一个位置存储12(栈);让变量a和12关联在一起(定义:赋值)
var a=[1,2],声明变量a;开辟一个新的内存空间存储[1,2](堆);让变量a和该对内存地址关联起来
如果是函数的话,会开辟一个新的内存空间存储代码字符串,执行的时候开辟一个私有作用域(栈)把之前创建函数时存储的字符串执行,执行完毕,该栈内存销毁。
变量提升
js是先预编译再执行,预编译的过程就是扫一遍把变量绑定作用域
。所以才有变量提升。
带var的只声明未定义,带function的把声明定义都完成了
console.log(a)
console.log(sum)
var a = 1
function sum () {
console.log('hello world')
}
执行结果是
【注意】
私有作用域,先形参赋值再变量提升再代码执行
带var和不带var的区别
在全局作用域下声明一个变量,也相当于给window对象设置了一个属性,属性的值就是属性值(私有作用域声明的私有变量和window没关系)
console.log(a)//undefined 变量提升不报错
conosle.log(window.a)//undefined 给window设置一个对象
console.log('a' in window)//true window存在a属性
var a = 12
console.log(a) //12
conosle.log(window.a)//12
如果不带var,看以下代码
a = 12
conosle.log(a)//12
console.log(window.a)//12
console.log(a)//报错 不执行
a = 12
conosle.log(a)
console.log(window.a)
console.log(window.a)//undefined
console.log('a' in window)//false
a = 12//=>window.a = 12
conosle.log(a)//12
console.log(window.a)//12
【总结】
在全局作用域
- 加var是全局变量,由于window对象和全局有映射关系,也就设置了window的属性
- 不加var的本质是window下的属性,自然没有变量提升
【拓展】
在私有作用域中
- 带var的在私有作用域有变量提升,都声明为死于变量,和外界没有任何关系
- 不带var不是私有变量,它会向它的上级作用域查找,看是否为上级变量,不是则继续向上查找,一直查找到window位置,这种查找机制叫做私有作用域链
作用域链的拓展
function fn () {
//变量提升:无
b = 13
console.log('b' in window)//true
console.log(b)
}
fn();//13
console.log(b)//13
在作用域链查找过程中,如果找到win也没有这个变量,相当于给win设置了一个属性b(window.b=13)
闭包
什么是闭包
- ·存在没被释放的内存
- 当前作用域使用其他作用域的变量
比如
function b () {
var a = 1
return function c () {
console.log(a)
}
}
var e = b()
e()//输出1
这就是个闭包,b执行完了栈内存也没有被释放,里面的a被b栈内存以外的变量引用。再看e,执行的时候器作用域是全局作用域,但使用到了b的私有作用域下的变量a
闭包的应用
- 为了保证js 的性能(堆栈内存的性能优化),应该尽可能减少闭包的使用(不被销毁的堆栈内存是耗性能的)
- 闭包具有保护作用,保护私有变量不受外界干扰
在项目中,尤其是团队写作开发的时候,应尽可能减少全局变量的使用,以防止相互之间的冲突,那么此时哦我们可以把自己的一部分内容封装到一个闭包里,让全局变量变为私有变量。基于return把需要供外面使用的方法暴露出去。
var Zepto = (function () {
//...
return {
xxx:function () {
}
}
})
或者,当想改变一个对象的值,比如user,之前用user.name,但这个方法不够好,当程序过于复杂,会一不小心改动一些东西,变化的轨迹就非常难以追踪。虽然有效但非常危险。所以想把修改过程管控起来,就用到了闭包。
可以定义一个function user(name)。在里面var一个age,sex。这时候返回的不是一个确切的数值,是一个对象,对象里包含不同的方法,比如getName,setName。方法就是定义在对象里面的函数。
当做的项目内部数据流足够复杂,那我们此处必须得找个办法来归纳,不能让对象里面的值在里面被随便修改,必须得找一个像一个接口一样的东西,必须得按我的方法才能修改和设置某个值,这样严谨很多。闭包可以通过走子函数来获取母函数的函数作用域,然后去存取里面的值。这就是闭包的好处和特点。
3. 闭包具有保存作用,形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用
可以基于闭包解决用var for循环给多个按钮绑定事件,无论点击第几个i都是最后那个。(这个问题可以直接直接用let解决,此处说用闭包解决的办法)
假如有五个按钮
for (var i=0; i<buttonList.length;i++) {
buttonList[i].onclick = function () {
console.log(i)
}
}//此时无论点击第几个按钮打印的都是4,因为js在es6之前没有块级作用域。当我们点击的时候,外层循环已经结束(可以点击的时候页面已经加载完成,页面加载完成预示着js代码都已经执行完成,也就是循环也都执行完成,外层循环结束已经让全局下的i为4)
用闭包解决如下:
for (var i=0; i<buttonList.length;i++) {
buttonList[i].onclick = (function () {
//让自执行函数执行,把执行的返回值(return)赋值给onclick,此时onclick绑定的是返回的小函数,点击的时候执行的是小函数,自执行函数在给事件赋值的时候已经执行了
return function () {
console.log(i)//上级作用域,自执行函数形成的作用域
}
})()
}