與閉包有關(guān)的概念:變量的作用域和變量的生存周期。下面本篇文章就來給大家介紹一下JS中變量的作用域及閉包,有一定的參考價值,有需要的朋友可以參考一下,希望對大家有所幫助。

一、變量的作用域
1、變量的作用域指變量有效的范圍,與變量定義的位置密切相關(guān),作用域是從空間的角度來描述變量的,也可以理解為變量的可見性。在某個范圍內(nèi)變量是可見的,也就是說,變量是可用的。
2、按照作用域的不同,變量可分為全局變量和局部變量。
● 全局變量:在全局環(huán)境中聲明的變量
● 局部變量:在函數(shù)中聲明的變量
● 當(dāng)函數(shù)在執(zhí)行時,會創(chuàng)建一個封閉的執(zhí)行期上下文環(huán)境,函數(shù)內(nèi)部聲明的變量僅可在函數(shù)內(nèi)部使用,外部無法訪問,而全局變量則在任何地方都可以使用
3、在函數(shù)中使用var關(guān)鍵字顯示聲明的變量是局部變量;而沒有用var關(guān)鍵字,用直接賦值的方式聲明的變量是全局變量
var m=8;
function f1(){
? ? var a1=10;
? ? console.log(m);? //8
}
function f2(){
? ? var a2=20;
? ? console.log(m);? //8
}
f1();
f2();
4、依賴變量作用域?qū)崿F(xiàn)封裝特性
(1)使用ES6提供的let
(2)通過函數(shù)來創(chuàng)建作用域:
var myObject=(function(){
? ? var _name="yian";? //私有變量
? ? return {
? ? ? ? getName:function(){? //公有方法
? ? ? ? ? ? return _name;
? ? ? ? }
? ? }
})();
console.log(myObject._name);? //undefined
console.log(myObject.getName());? //yian
二、變量的生存周期
1、對于全局變量來說,其生命周期是永久的,除非主動銷毀此全局變量;
2、對于在函數(shù)內(nèi)用var關(guān)鍵字聲明的局部變量來說,當(dāng)退出函數(shù)時,這些局部變量即失去它們的價值,它們會隨著函數(shù)調(diào)用的結(jié)束而被銷毀
3、模仿塊級作用域
(1)用作塊級作用域的匿名函數(shù):將函數(shù)聲明包含在一對圓括號中,表示它實(shí)際上是一個函數(shù)表達(dá)式,而緊隨其后的另一對圓括號會立即調(diào)用這個函數(shù)。
(function(){
? ? //這里是塊級作用域
})();
在匿名函數(shù)中定義的任何變量,都會在執(zhí)行結(jié)束時被銷毀
(2)先定義一個函數(shù),然后調(diào)用它。定義函數(shù)的方式是創(chuàng)建一個匿名函數(shù),并把這個匿名函數(shù)賦值給變量;而調(diào)用函數(shù)的方式是在函數(shù)的名稱后添加一對圓括號。
var someFunction=function(){
? ? //這里是塊級作用域
};
someFunction();
經(jīng)典問題:
var nodes=document.getElementsByTagName("div");
for(var i= 0,len=nodes.length;i<len;i++){
? ? nodes[i].onclick=function(){
? ? ? ? console.log(i);? //無論點(diǎn)擊哪個div,最后彈出的結(jié)果都是5
? ? }
}
解釋:?div節(jié)點(diǎn)的onclick事件是被異步觸發(fā)的,當(dāng)事件被觸發(fā)時,for循環(huán)早已結(jié)束,此時i 已經(jīng)是5
解決辦法:
法一:使用ES6中的let
法二:在閉包的幫助下,把每次循環(huán)的i值都封存起來
var nodes=document.getElementsByTagName("div");
for(var i= 0,len=nodes.length;i<len;i++){
? ? (function(x){
? ? ? ? nodes[i].onclick=function(){
? ? ? ? ? ? console.log(x);
? ? ? ? }
? ? })(i);
}
4、作用:讀取函數(shù)內(nèi)部的變量,并將這些變量的值始終保存在內(nèi)存中
(1)封裝變量:閉包可以把一些不需要暴露在全局的變量封裝為“私有變量”,私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)內(nèi)部定義的其他函數(shù)。
例:mult函數(shù)接收number類型的參數(shù),并返回這些參數(shù)的乘積、
初始代碼:
var cache={ };
var mult=function(){
? ? var args=Array.prototype.join.call(arguments,",");
? ? if(cache[args]){
? ? ? ? return cache[args];
? ? }
? ? var a=1;
? ? for(var i=0,len=arguments.length;i<len;i++){
? ? ? ? a=a*arguments[i];
? ? }
? ? return cache[args]=a;
};
console.log(mult(1,2,3));? //6
console.log(mult(3,4,5));? //60
使用閉包后:
var mult=(function(){
? ? var cache={ };? //加入緩存機(jī)制,避免相同參數(shù)的計算
? ? var calculate=function(){
? ? ? ? var a=1;
? ? ? ? for(var i= 0,len=arguments.length;i<len;i++){
? ? ? ? ? ? a=a*arguments[i];
? ? ? ? }
? ? ? ? return a;
? ? };
? ? return function(){
? ? ? ? var args=Array.prototype.join.call(arguments,",");
? ? ? ? if(args in cache){
? ? ? ? ? ? return cache[args];
? ? ? ? }
? ? ? ? return cache[args]=calculate.apply(null,arguments);
? ? }
})();
補(bǔ)充:in判斷屬性屬于對象
var mycar = {make: "Honda", model: "Accord", year: 1998};
if ( "make" in mycar ){? //屬性名必須是字符串形式,因?yàn)閙ake不是一個變量
? ? document.write('true');? // 顯示true
}
else{
? ? document.write('false');
}
(2)延續(xù)局部變量的壽命
例:使用report數(shù)據(jù)上報時會丟失30%左右的數(shù)據(jù),原因是img時report中的局部變量,當(dāng)report函數(shù)調(diào)用結(jié)束后,img局部變量隨即被銷毀
初始代碼:
var report=function(src){
? ? var image=new Image();
? ? image.src=src;
};
使用閉包后(把img變量用閉包封裝起來):
var report=(function(){
? ? var imgs=[ ];
? ? return function(){
? ? ? ? var image=new Image();
? ? ? ? imgs.push(image);
? ? ? ? image.src=src;
? ? }
})();
5、閉包和面向?qū)ο笤O(shè)計
閉包寫法:
var extent=function(){
? ? var value=0;
? ? return {
? ? ? ? call:function(){
? ? ? ? ? ? value++;
? ? ? ? ? ? console.log(value);
? ? ? ? }
? ? }
}
var extent=extent();
extent.call();? //1
extent.call();? //2
面向?qū)ο髮懛ㄒ唬?/p>
var extend={
? ? value:0,
? ? call:function(){
? ? ? ? this.value++;
? ? ? ? console.log(this.value);
? ? }
};
extend.call();? //1
extend.call();? //2
面向?qū)ο髮懛ǘ?/p>
var Extend=function(){
? ? this.value=0;
};
Extend.prototype.call=function(){
? ? this.value++;
? ? console.log(this.value);
};
var extend=new Extend();
extend.call();? //1
extend.call();? //2
6、閉包與內(nèi)存管理
● 局部變量變量應(yīng)該在函數(shù)退出時被解除引用,但如果局部變量被封閉在閉包形成的環(huán)境中,那么局部變量就會一直生存下去,即它會常駐內(nèi)存。
● 使用閉包的同時比較容易形成循環(huán)引用,如果閉包的作用域鏈中保存著一些DOM節(jié)點(diǎn),這就有可能造成內(nèi)存泄漏。
● 解決循環(huán)引用帶來的內(nèi)存泄漏問題:把循環(huán)引用中的變量設(shè)為null。(將變量設(shè)置為null以為著切斷變量與它之前引用的值之間的連接,當(dāng)垃圾收集器下次運(yùn)行時,就會刪除這些值并回收它們占用的內(nèi)存)
7、特點(diǎn):
● 函數(shù)嵌套函數(shù);
● 在函數(shù)內(nèi)部可引用外部的參數(shù)和變量;
● 參數(shù)和變量不會以垃圾回收機(jī)制回收。
8、優(yōu)點(diǎn):避免全局變量的污染
9、缺點(diǎn):會常駐內(nèi)存,增加內(nèi)存的使用量,使用不當(dāng)會造成內(nèi)存泄漏;閉包會攜帶包含它的函數(shù)的作用域,因此會比其他函數(shù)占用更多的內(nèi)存
10、創(chuàng)建閉包
寫法一:
function a() {?
var b=123;?
function c(){
? ? console.log(b+=1);
? ? }?
? ? return c;
}
var d=a();
d();
方式二:
function f1(){
? ? var num=10;? //函數(shù)執(zhí)行完畢,變量仍然存在
? ? var f2=function(){
? ? ? ? num++;
? ? ? ? console.log(num);? //11
? ? };
? ? return f2;
}
var res=f1();
res();
● 解釋:執(zhí)行f1()后,f1()閉包內(nèi)部的變量會存在,而閉包內(nèi)部函數(shù)的內(nèi)部變量不會存在,使得JavaScript的垃圾回收機(jī)制不會收回f1()占用的資源,因?yàn)閒1()中內(nèi)部函數(shù)的執(zhí)行需要依賴f1()中的變量。
方式三:
function foo(x) {
? ? var tmp = 3;
? ? return function f2(y) {
? ? ? ? alert(x + y + (++tmp));? //17
? ? };
}
var bar = foo(3);? //bar現(xiàn)在是一個閉包
bar(10);
練習(xí)題:
function f1(){
? var a=1;
? t=function(){
? ? ? a++;
? }
? return function(){
? ? ? console.log(a);
? }
}
var b=f1();? //返回值為一個匿名函數(shù)
b();? //1
t();
b();? //2
聲明變量,若變量名稱相同,就近原則:
var name="g";
function out(){
? ? var name="loc";
? ? function foo(){
? ? ? ? console.log(name);
? ? }
? ? foo();
}
out();? //name=loc
補(bǔ)充知識點(diǎn):
1、JS中有哪些垃圾回收機(jī)制?
(1)引用計數(shù):跟蹤記錄每個值被使用的次數(shù)。
● 當(dāng)聲明一個變量并將一個引用類型賦值給該變量時,該值的引用次數(shù)加1;
● 若該變量的值變?yōu)榱硪粋€,則該值引用次數(shù)減1;
● 若該值引用次數(shù)為0時,說明變量沒有在使用,此值無法訪問;
● 因此,可以將它占用的空間回收,垃圾回收機(jī)制會在運(yùn)行時清理引用次數(shù)為0 的值所占用的空間。
● 在低版的IE中會發(fā)生內(nèi)存泄漏,很多時候就是因?yàn)樗捎靡糜嫈?shù)得到方式進(jìn)行垃圾回收(如果兩個對象之間形成了循環(huán)引用,那么這兩個對象都無法被回收)。
(2)標(biāo)記清除:最常見的垃圾回收方式
● 當(dāng)變量進(jìn)入執(zhí)行環(huán)境時,垃圾回收器將其標(biāo)為“進(jìn)入環(huán)境”,離開時標(biāo)記為“離開環(huán)境”;
● 垃圾回收機(jī)制在運(yùn)行時給存儲在內(nèi)存中的所有變量加上標(biāo)記;
● 去掉環(huán)境中的變量及被環(huán)境中變量所引用的變量(閉包)的標(biāo)記;
● 完成這些后仍存在的標(biāo)記就是要刪除的變量。
2、哪些操作會造成內(nèi)存泄漏?
● 內(nèi)存泄漏:指不再擁有或需要任何對象(數(shù)據(jù))之后,它們?nèi)匀淮嬖谟趦?nèi)存中。
● 垃圾回收器定期掃描對象,并計算引用了每個對象的其他對象的數(shù)量。如果一個對象的引用數(shù)量為0(沒有其他對象引用過該對象),或?qū)υ搶ο蟮奈ㄒ灰檬茄h(huán)的,那么該對象占用的內(nèi)存立即被回收。
● 如果setTimeout的第一個參數(shù)使用字符串而非函數(shù),會造成內(nèi)存泄漏。
● 閉包、控制臺日志、循環(huán)(在兩個對象彼此引用且彼此保留時,就會產(chǎn)生一個循環(huán))等會造成內(nèi)存泄漏。
更多前端開發(fā)知識,請查閱 HTML中文網(wǎng) ??!