首頁 >web前端 >js教程 >淺談JavaScript變數的作用域及閉包

淺談JavaScript變數的作用域及閉包

青灯夜游
青灯夜游轉載
2019-11-25 14:47:202106瀏覽

與閉包有關的概念:變數的作用域和變數的生存週期。以下這篇文章就來跟大家介紹一下JavaScript中變數的作用域及閉包,有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

淺談JavaScript變數的作用域及閉包

一、變數的作用域

#1、變數的作用域指變數有效的範圍,與變數定義的位置密切相關,作用域是從空間的角度來描述變數的,也可以理解為變數的可見性。在某個範圍內變數是可見的,也就是說,變數是可用的。 【相關課程推薦:JavaScript影片教學

2、依作用域的不同,變數可分為全域變數和局部變數。

● 全域變數:在全域環境中宣告的變數

● 局部變數:在函數中宣告的變數

● 當函數在執行時,會建立一個封閉的執行期上下文環境,函數內部聲明的變數僅可在函數內部使用,外部無法訪問,而全局變數則在任何地方都可以使用

3、在函數中使用var關鍵字顯示聲明的變數是局部變數;而沒有用var關鍵字,用直接賦值的方式宣告的變數是全域變數

var m=8;
function f1(){
    var a1=10;
    console.log(m);  //8
}
function f2(){
    var a2=20;
    console.log(m);  //8
}
f1();
f2();

4、依賴變數作用域實現封裝特性

(1)使用ES6提供的let
(2)透過函數來建立作用域:

var myObject=(function(){
    var _name="yian";  //私有变量
    return {
        getName:function(){  //公有方法
            return _name;
        }
    }
})();
console.log(myObject._name);  //undefined
console.log(myObject.getName());  //yian

#二、變數的生存週期

##1、對於全域變數來說,其生命週期是永久的,除非主動銷毀此全域變數;

2、對於在函數內以var關鍵字宣告的局部變數來說,當退出函數時,這些局部變數即失去它們的價值,它們會隨著函數呼叫的結束而被銷毀

3、模仿區塊級作用域

(1)用作區塊級作用域的匿名函數:將函數宣告包含在一對圓括號中,表示它實際上是一個函數表達式,而緊接在後的另一對圓括號會立即呼叫這個函數。

(function(){
    //这里是块级作用域
})();

在匿名函數中定義的任何變量,都會在執行結束時被銷毀

(2)先定義一個函數,然後呼叫它。定義函數的方式是創建一個匿名函數,並將這個匿名函數賦值給變數;而呼叫函數的方式是在函數的名稱後面加上一對圓括號。

var someFunction=function(){
    //这里是块级作用域
};
someFunction();

經典問題:

var nodes=document.getElementsByTagName("div");
for(var i= 0,len=nodes.length;i<len;i++){
    nodes[i].onclick=function(){
        console.log(i);  //无论点击哪个div,最后弹出的结果都是5
    }
}

解釋: div節點的onclick事件是被非同步觸發的,當事件被觸發時,for循環早已結束,此時i 已經是5

解決方法:

#法一:使用ES6中的let

法二:在閉包的幫助下,把每次循環的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、作用:讀取函數內部的變量,並將這些變數的值始終保存在記憶體中

#( 1)封裝變數:閉包可以把一些不需要暴露在全域的變數封裝為“私有變數”,私有變數包括函數的參數、局部變數和其他在函數內部定義的函數。

範例:mult函數接收number類型的參數,並傳回這些參數的乘積、

#初始程式碼:

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={ };  //加入缓存机制,避免相同参数的计算
    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);
    }
})();

補充:in判斷屬性屬於物件

var mycar = {make: "Honda", model: "Accord", year: 1998};
if ( "make" in mycar ){  //属性名必须是字符串形式,因为make不是一个变量
    document.write(&#39;true&#39;);  // 显示true
}
else{
    document.write(&#39;false&#39;);
}

(2)延續局部變數的壽命

#例:使用report資料上報時會遺失30%左右的數據,原因是img時report中的局部變量,當report函數呼叫結束後,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、閉包與物件導向設計

閉包寫入法:

var extent=function(){
    var value=0;
    return {
        call:function(){
            value++;
            console.log(value);
        }
    }
}
var extent=extent();
extent.call();  //1
extent.call();  //2

物件導向寫法一:

var extend={
    value:0,
    call:function(){
        this.value++;
        console.log(this.value);
    }
};
extend.call();  //1
extend.call();  //2

物件導向寫法二:

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、閉包與記憶體管理

● 局部變數變數應該在函數退出時被解除引用,但如果局部變數被封閉在閉包形成的環境中,那麼局部變數就會一直生存下去,即它會常駐記憶體。

● 使用閉包的同時比較容易形成循環引用,如果閉包的作用域鏈中保存著一些DOM節點,這就有可能造成記憶體洩漏。

● 解決循環引用帶來的記憶體洩漏問題:把循環引用中的變數設為null。 (將變數設為null以為切斷變數與它之前引用的值之間的連接,當垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的記憶體)

7、特點:

● 函數巢狀函數;

● 在函數內部可引用外部的參數與變數;

##● 參數和變數不會以垃圾回收機制回收。

8、優點:避免全域變數的污染

9、缺点:会常驻内存,增加内存的使用量,使用不当会造成内存泄漏;闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存

10、创建闭包

写法一:

function a() {  
	var b=123;  
	function c(){
    	console.log(b+=1);
    }  
    return c;
}
var d=a();
d();

方式二:

function f1(){
    var num=10;  //函数执行完毕,变量仍然存在
    var f2=function(){
        num++;
        console.log(num);  //11
    };
    return f2;
}
var res=f1();
res();

● 解释:执行f1()后,f1()闭包内部的变量会存在,而闭包内部函数的内部变量不会存在,使得JavaScript的垃圾回收机制不会收回f1()占用的资源,因为f1()中内部函数的执行需要依赖f1()中的变量。

方式三:

function foo(x) {
    var tmp = 3;
    return function f2(y) {
        alert(x + y + (++tmp));  //17
    };
}
var bar = foo(3);  //bar现在是一个闭包
bar(10);

练习题:

function f1(){
   var a=1;
   t=function(){
       a++;
   }
   return function(){
       console.log(a);
   }
}
var b=f1();  //返回值为一个匿名函数
b();  //1
t();
b();  //2

声明变量,若变量名称相同,就近原则:

var name="g";
function out(){
    var name="loc";
    function foo(){
        console.log(name);
    }
    foo();
}
out();  //name=loc

补充知识点:

1、JS中有哪些垃圾回收机制?

(1)引用计数:跟踪记录每个值被使用的次数。

● 当声明一个变量并将一个引用类型赋值给该变量时,该值的引用次数加1;

● 若该变量的值变为另一个,则该值引用次数减1;

● 若该值引用次数为0时,说明变量没有在使用,此值无法访问;

● 因此,可以将它占用的空间回收,垃圾回收机制会在运行时清理引用次数为0 的值所占用的空间。

● 在低版的IE中会发生内存泄漏,很多时候就是因为它采用引用计数得到方式进行垃圾回收(如果两个对象之间形成了循环引用,那么这两个对象都无法被回收)。

(2)标记清除:最常见的垃圾回收方式

● 当变量进入执行环境时,垃圾回收器将其标为“进入环境”,离开时标记为“离开环境”;

● 垃圾回收机制在运行时给存储在内存中的所有变量加上标记;

● 去掉环境中的变量及被环境中变量所引用的变量(闭包)的标记;

● 完成这些后仍存在的标记就是要删除的变量。

2、哪些操作会造成内存泄漏?

● 内存泄漏:指不再拥有或需要任何对象(数据)之后,它们仍然存在于内存中。

● 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的唯一引用是循环的,那么该对象占用的内存立即被回收。

● 如果setTimeout的第一个参数使用字符串而非函数,会造成内存泄漏。

● 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)等会造成内存泄漏。

本文来自 js教程 栏目,欢迎学习!

以上是淺談JavaScript變數的作用域及閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除