深入理解闭包系列第二篇——从执行环境角度看闭包
2016-08-02 19:12
579 查看
×
目录
[1]图示 [2]说明 [3]总结
【1】代码执行流进入全局执行环境,并对全局执行环境中的代码进入声明提升(hoisting)
【2】执行流执行第9行代码var baz = foo();,对a进行LHS查询,给baz赋值函数foo()
【3】执行流继续执行第9行代码var baz = foo();,调用foo()函数,此时执行流进入foo()函数执行环境中,对该执行环境中的代码进行声明提升过程。此时执行环境栈中存在两个执行环境,foo()函数为当前执行流所在执行环境
【4】执行流执行第2行代码var a = 2;,对a进行LHS查询,给a赋值2
【5】执行流执行第7行代码return bar;,将bar()函数作为返回值返回。按理说,这时foo()函数已经执行完毕,应该销毁其执行环境,等待垃圾加收。但因为其返回值是bar函数。bar函数中存在自由变量a,需要通过作用域链到foo()函数的执行环境中找到变量a的值,所以foo()函数不能销毁,只是从活动状态变成非活动状态;而全局执行环境变成活动状态
【6】执行流执行第10行代码baz();,通过在全局执行环境中查找baz的值,baz保存着foo()函数的返回值bar。所以这时执行baz(),会调用bar()函数,此时执行流进入bar()函数执行环境中,对该执行环境中的代码进行声明提升过程。此时执行环境栈中存在三个执行环境,bar()函数为当前执行流所在执行环境
在声明提升的过程中,由于a是个自由变量,需要通过bar()函数的作用域链bar() -> foo() -> 全局作用域进行查找,最终在foo()函数中也就是代码第2行找到var a = 2;,然后在foo()函数的执行环境中找到a的值是2,所以给a赋值2
【7】执行流执行第7行代码console.log(a);,调用内部对象console,并从console对象中log方法,将a作为参数传递进入。从bar()函数的执行环境中找到a的值是2,所以,最终在控制台显示2
【8】执行流执行第6行代码},bar()的执行环境被弹出执行环境栈,并被销毁,等待垃圾回收,控制权交还给全局执行环境
【9】当页面关闭时,所有的执行环境都被销毁
由于闭包占用内存空间,所以要谨慎使用闭包。尽量在使用完闭包后,及时解除引用,以便更早释放内存
// var all = document.getElementById('cnblogs_post_body').children;
var select = [];
for(var i = 1; i < all.length; i++){
if(all[i].getAttribute('id')){
if(all[i].getAttribute('id').match(/anchor\d+$/)){
select.push(all[i]);
}
}
}
var wheel = function(e){
e = e || event;
var data;
if(e.wheelDelta){
data = e.wheelDelta;
}else{
data = -e.detail * 40;
}
for(var i = 0; i < select.length; i++){
if(select[i].getBoundingClientRect().top > 0){
return;
}
if(select[i].getBoundingClientRect().top <= 0 && select[i+1]){
if(select[i+1].getBoundingClientRect().top > 0){
change(oCon.children[i+2])
}
}else{
change(oCon.children[select.length+1])
}
}
}
document.body.onmousewheel = wheel;
document.body.addEventListener('DOMMouseScroll',wheel,false);
var oCon = document.getElementById("content");
var close = oCon.getElementsByTagName('span')[0];
close.onclick = function(){
if(this.innerHTML == '显示目录'){
this.innerHTML = '×';
this.style.background = '';
oCon.style.border = '2px solid #ccc';
oCon.style.width = '';
oCon.style.height = '';
oCon.style.overflow = '';
oCon.style.lineHeight = '30px';
}else{
this.innerHTML = '显示目录';
this.style.background = '#3399ff';
oCon.style.border = 'none';
oCon.style.width = '60px';
oCon.style.height = '30px';
oCon.style.overflow = 'hidden';
oCon.style.lineHeight = '';
}
}
for(var i = 2; i < oCon.children.length; i++){
oCon.children[i].onmouseover = function(){
this.style.color = '#3399ff';
}
oCon.children[i].onmouseout = function(){
this.style.color = 'inherit';
if(this.mark){
this.style.color = '#3399ff';
}
}
oCon.children[i].onclick = function(){
change(this);
}
}
function change(_this){
for(var i = 2; i < oCon.children.length; i++){
oCon.children[i].mark = false;
oCon.children[i].style.color = 'inherit';
oCon.children[i].style.textDecoration = 'none';
oCon.children[i].style.borderColor = 'transparent';
}
_this.mark = true;
_this.style.color = '#3399ff';
_this.style.textDecoration = 'underline';
_this.style.borderColor = '#2175bc';
}
// ]]>
目录
[1]图示 [2]说明 [3]总结
前面的话
本文从执行环境的角度来分析闭包,先用一张图开宗明义,然后根据图示内容对代码进行逐行说明,试图对闭包进行更直观的解释图示
说明
下面按照代码执行流的顺序对该图示进行详细说明function foo(){ var a = 2; function bar(){ console.log(a); } return bar; } var baz = foo(); baz();
【1】代码执行流进入全局执行环境,并对全局执行环境中的代码进入声明提升(hoisting)
【2】执行流执行第9行代码var baz = foo();,对a进行LHS查询,给baz赋值函数foo()
【3】执行流继续执行第9行代码var baz = foo();,调用foo()函数,此时执行流进入foo()函数执行环境中,对该执行环境中的代码进行声明提升过程。此时执行环境栈中存在两个执行环境,foo()函数为当前执行流所在执行环境
【4】执行流执行第2行代码var a = 2;,对a进行LHS查询,给a赋值2
【5】执行流执行第7行代码return bar;,将bar()函数作为返回值返回。按理说,这时foo()函数已经执行完毕,应该销毁其执行环境,等待垃圾加收。但因为其返回值是bar函数。bar函数中存在自由变量a,需要通过作用域链到foo()函数的执行环境中找到变量a的值,所以foo()函数不能销毁,只是从活动状态变成非活动状态;而全局执行环境变成活动状态
【6】执行流执行第10行代码baz();,通过在全局执行环境中查找baz的值,baz保存着foo()函数的返回值bar。所以这时执行baz(),会调用bar()函数,此时执行流进入bar()函数执行环境中,对该执行环境中的代码进行声明提升过程。此时执行环境栈中存在三个执行环境,bar()函数为当前执行流所在执行环境
在声明提升的过程中,由于a是个自由变量,需要通过bar()函数的作用域链bar() -> foo() -> 全局作用域进行查找,最终在foo()函数中也就是代码第2行找到var a = 2;,然后在foo()函数的执行环境中找到a的值是2,所以给a赋值2
【7】执行流执行第7行代码console.log(a);,调用内部对象console,并从console对象中log方法,将a作为参数传递进入。从bar()函数的执行环境中找到a的值是2,所以,最终在控制台显示2
【8】执行流执行第6行代码},bar()的执行环境被弹出执行环境栈,并被销毁,等待垃圾回收,控制权交还给全局执行环境
【9】当页面关闭时,所有的执行环境都被销毁
总结
从上述说明的第5步可以看出,由于闭包bar()函数的原因,其父函数foo()的执行环境一直存在于内存中,就是为了能够使得调用bar()函数时,可以通过作用域链访问到父函数foo(),并得到其执行环境中储存的变量值。直到页面关闭,foo()函数的执行环境才会和全局执行环境一起被销毁,从而释放内存空间由于闭包占用内存空间,所以要谨慎使用闭包。尽量在使用完闭包后,及时解除引用,以便更早释放内存
//通过将baz置为null,解除引用 function foo(){ var a = 2; function bar(){ console.log(a);//2 } return bar; } var baz = foo(); baz(); baz = null; /*后续代码*/
// var all = document.getElementById('cnblogs_post_body').children;
var select = [];
for(var i = 1; i < all.length; i++){
if(all[i].getAttribute('id')){
if(all[i].getAttribute('id').match(/anchor\d+$/)){
select.push(all[i]);
}
}
}
var wheel = function(e){
e = e || event;
var data;
if(e.wheelDelta){
data = e.wheelDelta;
}else{
data = -e.detail * 40;
}
for(var i = 0; i < select.length; i++){
if(select[i].getBoundingClientRect().top > 0){
return;
}
if(select[i].getBoundingClientRect().top <= 0 && select[i+1]){
if(select[i+1].getBoundingClientRect().top > 0){
change(oCon.children[i+2])
}
}else{
change(oCon.children[select.length+1])
}
}
}
document.body.onmousewheel = wheel;
document.body.addEventListener('DOMMouseScroll',wheel,false);
var oCon = document.getElementById("content");
var close = oCon.getElementsByTagName('span')[0];
close.onclick = function(){
if(this.innerHTML == '显示目录'){
this.innerHTML = '×';
this.style.background = '';
oCon.style.border = '2px solid #ccc';
oCon.style.width = '';
oCon.style.height = '';
oCon.style.overflow = '';
oCon.style.lineHeight = '30px';
}else{
this.innerHTML = '显示目录';
this.style.background = '#3399ff';
oCon.style.border = 'none';
oCon.style.width = '60px';
oCon.style.height = '30px';
oCon.style.overflow = 'hidden';
oCon.style.lineHeight = '';
}
}
for(var i = 2; i < oCon.children.length; i++){
oCon.children[i].onmouseover = function(){
this.style.color = '#3399ff';
}
oCon.children[i].onmouseout = function(){
this.style.color = 'inherit';
if(this.mark){
this.style.color = '#3399ff';
}
}
oCon.children[i].onclick = function(){
change(this);
}
}
function change(_this){
for(var i = 2; i < oCon.children.length; i++){
oCon.children[i].mark = false;
oCon.children[i].style.color = 'inherit';
oCon.children[i].style.textDecoration = 'none';
oCon.children[i].style.borderColor = 'transparent';
}
_this.mark = true;
_this.style.color = '#3399ff';
_this.style.textDecoration = 'underline';
_this.style.borderColor = '#2175bc';
}
// ]]>
相关文章推荐
- 深入理解闭包系列第二篇——从执行环境角度看闭包
- 深入理解JavaScript - 闭包 (二)从执行环境角度看闭包
- 深入理解javascript原型和闭包系列 深入理解javascript原型和闭包(13)-【作用域】和【上下文环境】
- 深入理解javascript作用域系列第五篇——一张图理解执行环境和作用域
- 【译】深入理解JavaScript系列:1. 执行环境
- 深入理解javascript原型和闭包系列 深入理解javascript原型和闭包(9)——简述【执行上下文】下
- Glide系列第二弹,从源码的角度深入理解Glide的执行流程
- Glide系列之二:从源码的角度深入理解Glide的执行流程
- 深入理解javascript原型和闭包系列 深入理解javascript原型和闭包(8)——简述【执行上下文】上
- 深入理解javascript原型和闭包系列 系深入理解javascript原型和闭包(17)——补充:上下文环境和作用域的关系
- 深入理解JavaScript系列(11) 执行上下文(Execution Contexts)
- 深入理解JavaScript系列(11):执行上下文(Execution Contexts)
- 深入理解JavaScript系列(16):闭包(Closures)
- 深入理解JavaScript系列(16) 闭包(Closures)
- 深入理解JavaScript系列(11):执行上下文(Execution Contexts)
- javascript笔记:通过对作用域链和执行环境的深入理解所得出的提高javascript代码性能的建议
- javascript笔记:通过对作用域链和执行环境的深入理解所得出的提高javascript代码性能的建议
- 深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解
- javascript笔记:通过对作用域链和执行环境的深入理解所得出的提高javascript代码性能的建议
- 深入理解JavaScript系列 ----(16):闭包(Closures)