fn();// ‘b‘
function fn(){
console.log(‘a‘)
}
var fn;
function fn(){
console.log(‘b‘)
}
fn(); // ‘b‘
//这里最终两次都是输出b 我个人猜测JavaScript引擎实际上是这么做的
/*
全局作用域预编译环境 内存先开辟一块栈空间(这时候开辟的是全局作用域栈) 用来存放全局作用域的变量 内存空间分为栈和堆两种 下面有解释
*/
/*
第一步:
预编译环境妹妹和栈哥哥(全局作用域,函数作用域栈的问题不在这里讨论,下面说的栈都是全局作用域栈)以及堆哥哥的对话
妹妹: 栈哥哥,你有没有一块名字叫fn的空间
栈哥哥: 我现在没有
妹妹: 哦,那你帮我开辟一块好不好,我现在需要
栈哥哥: 好的,现在有了
预编译环境妹妹:谢谢栈哥哥,你帮我把
function (){
console.log(‘a‘)
}
存到那里面去吧
栈哥哥:好的,不过你这个是引用类型哦,我帮你存到我兄弟堆空间里面去吧,等会把堆的地址给你,你要用的话就拿这个地址去找它就好了(解释一下,内存分两种,一种是栈空间(作用域,栈先进后出,全局作用域最先被声明,但是最后被销毁,它要等到该作用域里所有变量的引用都消失的时候才会销毁,这也就是为什么滥用闭包会造成内存泄露的原因,一旦一个变量一直被引用,那么包含它的所有作用域都不会被销毁),用来存值类型,一种是堆空间,用来存引用数据类型,如object(普通对象、数组、日期对象、正则对象等等)、function,栈空间里存了堆空间的引用地址(指针),可以根据这个地址找到堆空间里你要的函数或者对象)。然后对堆说:堆兄弟,帮我开辟一块空间,把这东西放进去,然后把地址给我
堆哥哥: 栈兄弟,我存好了,地址给你。反手扔过去一个存函数的地址。
栈哥哥: 妹妹,地址给你,存好别丢了哦,要的话找我就行。
预编译环境妹妹: 好的,谢谢栈哥哥,么么哒。(堆内心OS:我呢?怎么不谢谢我?哎,以后请叫我雷锋。)
*/
function fn(){
console.log(‘a‘)
}
/*
第二步:
预编译环境妹妹: 栈哥哥,又要来麻烦你了,你有没有一块名字fn的空间
内存哥哥: 你刚才问过了,现在有
预编译环境妹妹: 不好意思啊,我有间断性失忆,那没事了(js引擎不会对已经声明的变量重复声明,会忽略后面的声明,只有第一次声明有效)
*/
var fn;
/*
第三步:
预编译环境妹妹: 栈哥哥,又要来麻烦你了,你有没有一块名字fn的空间
内存哥哥: 你问过两遍了,这是第三遍了,我说了,现在有
预编译环境妹妹: 问过了吗,我忘了,我有间断性失忆,不好意思啊,啊,对了麻烦你帮我把
function (){
console.log(‘b‘)
}
存到里面去
内存哥哥:好的,我帮你存好了(雷锋,帮我存一下这个),地址给你(这里有两种可能,同样是引用类型赋值,一种可能是把之前的堆空间销毁,重新开辟一个空间储存,把新的地址存到栈里,第二种可能就是直接通过地址找到那个堆空间,然后把里面的干掉,然后鸠占鹊巢,我比较倾向于第二种情况)
预编译环境妹妹: 好的,谢谢哥哥,你忙吧,不打扰你了,么么哒(雷锋:又没我啥事?)
*/
function fn(){
console.log(‘b‘)
}
//执行代码的时候
/*
全局执行环境妹妹和栈哥哥,堆哥哥的对话
妹妹: 栈哥哥,你有没有fn
栈哥哥: 有,给你(栈哥哥发现是一个地址后就拿这个地址去找堆兄弟。雷锋,帮我拿一下这个,小妹要用。雷锋:给你赶紧滚,见色忘义的家伙,也不知道提一下我的存在),你用完了和我说一下
妹妹: 好的,谢谢哥哥,我要干活了(拿到的那个函数是执行console.log(‘b‘),调用这个函数的时候会生成一个函数作用域,也是先进行变量和函数的提升,在内存里开辟一块栈空间用来储存这个函数作用域里的变量,当执行完函数之后,如果这个作用域里的变量没有被引用的,那么会告知内存,销毁这个作用域栈空间,函数每次调用的时候都会生成一个作用域,也就是会新开辟一块栈空间)
*/
fn();
/*
妹妹: 栈哥哥,又来找你了,你有没有fn
栈哥哥: 给你(同上)
妹妹: 我执行完了,没有引用的变量了
全局作用域栈这个时候感觉到fn已经不被需要了,同时自己也不被需要了,身体已经被掏空了,于是把fn空间(只是栈里面的)销毁后进行了自我销毁(GC回收)
堆这个时候感觉到那个函数已经不被需要了,于是把那块储存那块函数的内存释放了(GC回收) (销毁和释放只是说法不同而已,相当于同义词)
*/
fn();