返回列表 发新帖

JavaScript 闭包

[复制链接]

6

主题

53

回帖

198

积分

小学生

Rank: 2

金币
110
好评
0
信誉
100

MT论坛新人

发表于 2023-4-21 21:52:24 来自手机  | 显示全部楼层 | 阅读模式  来自 广东
1.基本知识1.1 作用域1.1.1 函数作用域与全局作用域function foo(){
    var a = 'bar';
    console.log(a);
};
foo()//输出函数内的a
对以上代码稍加改动:var b = 'bar';
function foo(){
    console.log(b);
};
foo()
//在函数作用域内未找到b,但是它会继续向外扩大查找范围,于是便在全局作用域中找到了变量b,并输出bar
再对代码稍加改动:function bar(){
    var d = 'bar';
}
function foo(){
    console.log(d);
}
foo()//报错,d没有定义
//foo和bar分别属于两个彼此独立的函数作用域。
//foo函数无法访问bar函数的变量d
看如下函数套函数的示例function bar(){
    var d = 'bar';
    function foo(){
        console.log(d);
    }
    foo()
}
bar()
d再往外面一层也能查找到,如下示例: var d = 'bar';
function bar(){
    function foo(){
        console.log(d);
    }
    foo()
}
bar()
我们看到,变量作用域的查找是一个扩散的过程,就像各个环节相扣的链条,逐次递进,这就是作用域链的由来。1.1.2块级作用域和暂时性死区function foo(){
    console.log(a);
    var a = 'bar';
};
foo()//undefined
输出undefined,原因是变量bar在函数内进行了提升,以下是等价代码:function foo(){
    var a;
    console.log(a);
    a = 'bar';
};
foo()//undefined
但是,使用let对bar进行声明时,则会报错function foo(){
    console.log(f);//Cannot access 'f' before initialization
    let f = 'bar';
};
foo()
var声明的时候,变量提升,会把变量赋值为undefinedlet和const赋值的时候,声明变量的时候会针对这个变量形成一个密闭的块级作用域,在这个块级作用域中,如果在声明变量前访问该变量,就会报referenceError错误图形化描述作用域如下:


图形化描述死区如下:


1.2执行上下文和调用栈代码执行的两个阶段代码预编译阶段代码执行阶段预编译过程的细节,应注意以下3点在预编译阶段进行变量声明在预编译阶段对变量声明进行提升,但是值为undefined在预编译阶段对所有非表达式的函数声明进行提升我们来看以下示例来理解如上注意点function bar (){
    console.log('bar1');
}
var bar = function(){
    console.log('bar2');
}
bar();//bar2
接着我们调换代码顺序,执行如下代码:var bar = function(){
    console.log('bar2');
}
function bar (){
    console.log('bar1');
}
bar();//bar2
结果还是bar2.因为预编译阶段虽然对变量bar进行了声明,但是并没有进行赋值;函数bar被创建并提升。在代码执行阶段,变量bar才会被赋值,赋值的内容是函数体为console.log('bar2');的函数,输出结果为bar2看以下示例:foo(10);
function foo(num){
    console.log(value);/undefined
    var value = num;
    console.log(value);//10
}
由于value会在函数体里面变量提升,因此第一个会输出undefined;赋值后,value输出10.思考一下,var改成let,函数运行又会是什么结果呢?2.闭包定义理解:函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,进而形成闭包。function numGenerator(){
    var num = 1;
    num++;
    return ()=>{
        console.log(num);
    }
}
var getNumber = numGenerator();
getNumber();
以上示例中,numGenerator创建了一个变量num,接着返回打印num的匿名函数,这个函数引用了变量num,使得在外部可以通过调用getNumber方法访问变量num。因此在numGenerator执行完毕后,即相关调用栈出栈后,变量number不会消失,仍然有机会被外界访问。运行的时候,我们可以看到JavaScripot引擎对执行过程的分析,如下图所示:


num值被标记为Closure,即闭包变量。

3.内存管理内存的生命周期:分配内存、读写内存、释放内存。var foo = "bar";//分配内存
alert(foo);//读写内存
foo = null;//释放内存
3.1内存管理基本概念内存空间主要分为栈空间和堆空间,具体如下。栈空间:由操作系统自动分配释放,存放函数的参考值、局部变量的值等,其操作方式类似于数据解构中的栈。堆空间:一般由开发者分配释放,关于这部分空间需要考虑垃圾回收问题。JavaScript 中,数据类型包括基本数据类型和引用类型:基本数据类型:如 undefined null number boolean string引用类型: 如object array function一般情况下,基本数据类型按照值大小保存在栈空间中,占有固定大小的内存空间;引用类型保存在堆空间中,内存空间大小并不固定,需按引用情况来进行访问。var a = 11;
var b = 10;
var c = [1,2,3];
var d = {e:20};
对应的内存分配如下所示:



内存泄漏是指内存空间明明不再被使用,但由于种种原因并没有被释放。因为内存空间是否还在使用在某种程度上市不可判定的,或者判定成本很高。内存泄漏的危害非常直观:它会直接导致程序运行缓慢,甚至奔溃。3.2 典型内存泄漏场景示例1var element = document.getElementById("element");
element.mark = "marked";
//移除额lement节点
function remove(){
    element.parentNode.removeChild(element);
}
上面的代码中,我们只是把id为element的节点移除了,但是变量element依然存在,该节点占有的内存无法被释放。为了解决这一问题,我们需要在remove方法中添加element=null,这样更为稳妥。示例2var element = document.getElementById("element");
element.innerHTML = "<button id="button">点击</button>"
var button = document.getElementById("button");
button.addEventListener("click",function(){
  //...
});
element.innerHTML = "";
执行以上代码,因为存在element.innerHTML =""这条语句,button元素已经从DOM中移除了,但是由于其事件处理句柄还在,所以该节点变量依然无法被回收。因此,我们还需要添加removeEventListener函数,以防止内存泄漏。3.3 浏览器垃圾回收引用计数:一个对象不被其他对象引用时会被回收。问题:循环引用时无法回收标记-清除:从根元素开始,周期性标记可被访问的对象,同时回收不可被访问的对象问题:收集垃圾时程序会等待,且回收后的内存空间不连续,于是出现了标记-整理机制,即回收后会整理内存空间,但效率又会降低一些良好习惯:对不再使用的数据解除引用、占空间大的全局变量用完后声明为null(开发者主动保证回收)3.4 闭包使用的注意示例示例1function foo(){
    var value = Math.random();
    function bar(){
         debugger;
    }
    return bar;
}
var bar = foo();
bar();


我们把函数bar中加上对value的引用function foo(){
    var value = Math.random();
    function bar(){
        console.log(value);
        debugger;
    }
    return bar;
}
var bar = foo();
bar();


4. 闭包使用的示例讲解4.1示例1var foo = (function(){
   var v  = 0;
   return ()=>{
       return v++;
   }
}());
for(let i = 0; i < 10; i++){
    console.log(foo());
}// 0 1 2 3 4 5 6 7 8 9
在循环执行foo时,引用自由变量10次,v自增10次,最后执行foo时,得到10.这里的自由变量是指没有在相关函数中声明,但却被使用了的变量。4.2 示例2var foo = ()=>{
    var arr = [];
    var i;
    for(i = 0;i<10;i++){
        arr=function(){
            console.log(i);
        }
    }
    return arr[0];
}
foo()();//10
自由变量i,执行foo返回的是arr[0],arr[0]此时是函数,其中变量i的值为10.4.3示例3var fn = null;
var foo = ()=>{
    var a = 2;
    function innerFoo(){
        console.log(a);
    }
    fn = innerFoo;
}
var bar = ()=>{
    fn1()
}
foo()
bar()//2
正常来讲,根据调用栈的知识,foo函数执行完毕后,其执行环境生命周期会结束,所占用的内存会被垃圾收集器释放,上下文消失、但是通过调用innerFoo函数赋值的全局变量fn,foo的变量对象a也会被保留下来。所以,函数fn在函数bar内部执行时,依然可以访问被保留下来的变量对象,输出结果为2.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

13

主题

5044

回帖

1万

积分

博士生

E●机甲FK

Rank: 7Rank: 7Rank: 7

金币
3850
好评
22
信誉
100

MT论坛最佳新人MT论坛帅哥MT论坛新人考神MT论坛活跃会员挂机大佬

QQ
发表于 2023-4-22 11:54:44 来自手机  | 显示全部楼层  来自 安徽
感谢分享,沙发拿来
回复

使用道具 举报

3

主题

4183

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
2715
好评
2
信誉
94

MT论坛最佳新人MT论坛新人考神

发表于 2023-4-30 19:44:43 来自手机  | 显示全部楼层  来自 广西
学习学习
回复

使用道具 举报

8

主题

3840

回帖

8798

积分

硕士生

Rank: 6Rank: 6

金币
2937
好评
6
信誉
96
发表于 2023-6-5 19:01:02 来自手机  | 显示全部楼层  来自 广西
路过看看
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表