要理解堆栈,先理解js的数据类型:
1.基本类型(也叫值类型):number,string,boolean,null,undefined
2.引用类型:object,function
3.特殊类型:symbol
熟悉了基本类型和引用类型后来看看什么叫栈内存吧!
栈内存stack:
1.提供一个供js执行的环境(js都是在栈中执行的)
2.存储js的基本数据类型,由于基本数据类型比较简单,所以都是直接在栈内存中开辟一个位置存储的。
=> 当栈内存被销毁,存储的那些基本值也跟着销毁了
堆内存heap:
1.存储引用类型值的(对象:键值对,函数:代码字符串)
=>当前堆内存被销毁时,里面的引用值也被销毁
=>堆内存的释放:当堆内存没有被任何的变量或者其他东西所占用,浏览器会在空闲的时候,自主的进行内存回收,把占用的堆内存销毁(谷歌浏览器是这样,但是ie不同)
xxx=null 通过空对象指针可以让其不再指向,那么原有被占用的堆内存就没有被东西占用,浏览器会销毁它
了解了堆栈的概念来做一下两道面试题吧!
alert(a) 的结果是’0’,字符串的0(因为alert弹出的都是字符串);
过程:
首先要明白,所有变量和值的关联都是通过指针来实现的,在执行上下文的时候,遇到变量赋值,
步骤都是:1.创建变量 2.创建值 3. 关联
所以在一个执行栈中如图所示:
然后就是b++,由于一个变量只能关联一个值,(但是一个值可以关联多个变量),所以
alert(o.a)是’10’;过程:
和上个步骤基本差不多,不过这里是通过创建堆,然后通过指针关联到指定地址而已。AAAFFF0000是十六进制的堆地址.
首先要理解a = b = 1的意思是 a = 1 ; b = 1;所以里面的 a.x = a = { n: 2 }也可以理解了。
这是前面两个let定义赋值
然后要再理解一个赋值的相关操作就是:
赋值的话,一个等号是从右到左,但是这个是赋值,连等的话如a.x=a=1,即分为a.x=1;a=1;是先a.x=1再a=1的,所以这个是从左到右的(如果这里的a和a.x换了顺序结果又会变化了)。
然后看下图:这里是先执行a.x = {n;2}的操作,要注意只要引用类型赋值,就会重新创建一个堆,所以这里是又创建了一个堆来存储n:2,然后x指向这个新堆的十六进制地址.
接下来是执行a = {n : 2},
所以可以看到,a指向新堆后,里面没有x,故此时的x.a是undefined,其实注意点就是:
1.引用类型赋值会创建新堆! 2.步骤是先创建值(堆)再进行指向。这些都是js的底层执行机制
然后让我们再了解一下GO/VO/AO/EC及作用域和执行上下文
GO:Global Object全局对象.
当浏览器打开每一个网页之初,都会默认创建一个globalObject对象,将我们常用的一些属性和方法都挂载上去,以供我们调用,都是一些内置的方法。所以我们经常有window.xxx来调用一些东西,this也是指最高全局变量window,而window经常被省略。
ECStack:Execution Context Stack 执行环境栈
EC:Execution Context 执行环境(执行上下文)
VO: Varibale Object 变量对象
AO: Activation Object 活动对象
VO可以理解为AO的集合,AO为VO的一个分支,一个特殊对象,一般函数执行的时候的函数叫做AO
Scope:作用域,创建的函数的时候就赋予了
Scope Chain : 作用域链
下面再做两道题
这里要注意:
1.不用let/var/const定义的为全局变量
2.let a = b = 13;的意思是 let a = 13;b = 13,也就是说这里的b = 13是全局变量。
明白这两点,这道题便可以解出来了, fn()的结果是 13,13,
console.log(a,b)的结果是12,13,b之所以是13是因为后面被改掉了。
过程不细说了,答案如下:
4,8,8,1
另外再看:
在栈中,即简单变量储存这块,var b = a;把a的存储的值放到一个新位置上,是直接操作值的,让新位置上的值和b保持关联,但此时的a和b是没有关系的!要记住是新开发的位置进行存储,而不是跟堆一样可以两个变量指向同一个值!
而在堆中可以由图看到,var ary2 = ary1,因此两个ary指向同一地址的堆,当ary中的值改变时,ary1也变化了!
下面再了解一下堆栈内存释放:
栈内存:
一般情况下,当函数执行完成,所形成的的私有作用域(栈内存)都会自己被释放掉,同时栈内存中存储的值也都会被释放,但也有特殊不销毁的情况:
1.函数执行完成,当前形成的栈内存中,某些内容被栈内存意外的变量占用了,此时的栈内存不能释放,因此一旦释放外面内容就会丢失
2.全局栈内存只有在页面关闭的时候才会被释放掉
…
如果当前栈内存没有被释放,那么之前在栈内存中存贮的基本值也不会被释放,能够一直保存下来.
堆内存:
让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用堆内存,浏览器会在空闲时候将它释放掉)
下面再来看一下闭包
闭包的定义有很多,如MDN的:函数和对周围状态(词法环境)的引用捆绑在一起构成闭包;也就是说,闭包可以让你从内部函数访问外部函数作用域。在js中,每当函数被创建,就会在函数生成时生成闭包。
其实大概意思都差不多:基本都是从闭包的功能进行说明的,闭包主要是为了保护里面的变量和属性不被外界所污染。
闭包的例子有:
柯理化函数:
惰性函数:
闭包在项目中的实战应用: 真实项目中为了保证js的性能(堆栈内存的性能优化),应该减少闭包的使用(不销毁的堆栈内存是消耗性能的)
1.闭包具有保护作用:保护私有变量不受外界的干扰;在项目开始时,尤其是团队协作开发时,应当尽可能的减少闭包的使用,但是为防止相互之前的冲突,也就说所谓的全局变量污染经常将全局变量转化为私有变量,即
弄成立即执行函数
2.闭包具有保存作用:形成不销毁的栈内存,把一些值保存使用
下面从 [[Environment]] 周围状态 的角度理解一下闭包
那什么是Environment呢,跟prototype原型对比理解一下:
再看一下ECMA的解释:
[[Environment]]: 使得函数“关闭”的词法环境,在函数代码运行时作为外部环境来使用。。
emm每个字都认识,但是连起来就看不懂了,没关系,外面继续往下看!
首先了解一下词法环境对象:
只关心前两个即可
这个公文包即一个词法环境对象,可以理解为作用域
理解为作用域后就很好理解了,fn函数的变量等其他东西都存在里面
当前函数的词法环境对象可以访问到外层函数的词法环境对象,直到全局的词法环境对象,相当于作用链。
然后再看下这道题:
结果想必大家都是知道的,需要注意的是什么呢:
函数实例被创建时会生成一个词法环境对象,也就是如图:从fout函数依次到外面寻找就像图画的一样
同理b的也是一样;
!重点是什么呢:
fa和fb是两个不同的函数,就是因为它们的词法环境对象不同,即它们的作用域也不同,虽然说内容一样,但在意义上是不一样的。
死锁问题的出现与解决:
解决就是通过闭包来解决啦:先i定义一个函数使用闭包,让其可以访问到外部的变量
文章是学习b站视频后的学习笔记