1. 程式人生 > 實用技巧 >自執行函式和閉包

自執行函式和閉包

自執行函式

自執行函式的好處:匿名函式自執行裡面的所有東西都是一個區域性的。防止和其他的程式碼衝突。

自執行函式的四種寫法

自執行函式的第一種寫法:最前最後加括號

這種寫法是jslint推薦的寫法,可以讓閱讀者清楚的看到這是一個整體。

注意:這種寫法必須保證js程式碼的結尾處以封號結尾,不然會報Uncaught TypeError的錯。

(function(global, factory){
	console.log(global+factory)
}(1,2));//3

自執行函式的第二種寫法:function外面加括號

第二種寫法相比較第一種寫法缺少了閱讀的整體性。

(function(global, factory){
	console.log(global+factory)
})(1,2);//3

自執行函式的第三種寫法:在function前面加運算子,常用的是!和void

!function(global, factory){
	console.log(global+factory)
}(1,2)//3
+ function(global, factory){
	console.log(global+factory)
}(1,2)//3
void function(global, factory){
    console.log(global+factory)
}(1,2)//3

自執行函式的第四種寫法:new function

 new function(){
   console.log(1)
 }
 new function(a){
     console.log(a)
 }(1)//傳遞引數的情況

訪問自執行函式裡面的變數

如果直接呼叫自執行函式中的方法或者變數會報錯,比如下面的程式碼會報:Uncaught ReferenceError: global is not defined。

(function(global, factory){
	var global, factory=3;
}())
console.log(global, factory)

為了能正確的訪問自執行函式中的變數,可以將對外提供的介面作為window的屬性或者是方法。

(function(window,global, factory){
	function getGlobalValue(){
		return global
	}
	window.getGlobalValue=getGlobalValue
}(window,2,3));
console.log(getGlobalValue())//2

注:在上述程式碼中記得傳入window,因為在壓縮時window既不是宣告的區域性變數也不是引數,所以不會被壓縮混淆的,但是傳入window是可以將其壓縮混淆,而且傳入window引數,就可以不用沿著作用域鏈一層層向上查詢直到頂層作用域去獲取window物件,這樣一來訪問的速度就更快了。

作用域

區域性變數

  1. 在函式內部宣告的變數
  2. 使用let宣告的變數

全域性變數

  1. 變數在函式外定義
  2. 變數在函式內沒有宣告(沒有使用 var 關鍵字)

注:在 JavaScript 中, 作用域為可訪問變數,物件,函式的集合。
在函式體內,區域性變數的優先順序高於同名的全域性變數。如果在函式內宣告一個區域性變數或者函式引數中帶有的變數和全域性變數重名,那麼全域性變數就被區域性變數全部覆蓋。

作用域鏈

當宣告一個函式時,區域性作用域一級一級向上包起來,就是作用域鏈。

1.當執行函式時,總是先從函式內部找尋區域性變數

2.如果內部找不到(函式的區域性作用域沒有),則會向建立函式的作用域(父級函式作用域)尋找,依次向上,直到找到為止

var global_variable1=10;
function test1(){
	var local_variable1={name:'html'};
	var global_variable1=1;
	global_variable2=11;
	local_variable2=[1,2];
	var local_variable2;
	function test2(){
		console.log(global_variable1);//如果在第4行聲明瞭和全域性變數global_variable1同名的區域性變數,則輸出1,如果沒有,則輸出10
	}
	console.log(global_variable1);//如果在第4行聲明瞭和全域性變數global_variable1同名的區域性變數,則輸出1,如果沒有,則輸出10
	console.log(local_variable1);//{name: "html"}
	console.log(global_variable2);//11
	console.log(local_variable2);//[1,2]js函式中宣告的變數(但是不涉及賦值)都被“提前”至函式體的頂部,變數初始化留在原來的位置。
	test2();
}
//console.log(local_variable);//Uncaught ReferenceError: local_variable is not defined
test1();
//test2();//Uncaught ReferenceError: test2 is not defined
console.log(global_variable2)//11

注:在上述程式碼中,當執行test2時,建立函式test2的執行環境,並將該物件置於連結串列開頭,然後將函式test1的呼叫物件放在第二位,最後是全域性物件,作用域鏈的連結串列的結構是test2——>test1——>window。從連結串列的開頭尋找變數global_variable1,即test2函式內部找變數global_variable1,發現沒有,繼續向上找,這時分為兩種可能:

  1. 當test1中沒有對global_variable1重新宣告時,則在test1中也沒有找到,繼續向上,找到window中的global_variable1變數,結果是10,
  2. 當test1中對global_variable1重新宣告時,則在test1中找到global_variable1變數,結果是1。

閉包

閉包是指可以訪問另一個函式作用域中變數的函式,由於在JavaScript中,只有在函式內部的子函式才可以訪問區域性變數,所以建立閉包的最常見的方式就是在一個函式內建立一個子函式函式,通過這個子函式訪問這個函式的區域性變數,利用閉包可以突破作用鏈域,將函式內部的變數和方法傳遞到外部。

閉包的特點:

1.函式內巢狀函式一個或多個子函式
2.內部函式可以引用外層的引數和變數
3.引數和變數不會被垃圾回收機制回收
JavaScript垃圾回收機制請看鄭文亮先生的部落格:https://www.cnblogs.com/zhwl/p/4664604.html

記憶體洩漏的情況舉例

記憶體洩漏(memory leak)是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

記憶體溢位(out of memory)是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory,比如申請了一個integer,但給它存了long才能存下的數,那就是記憶體溢位。
注:記憶體洩漏最終會導致記憶體溢位。

  1. 意外的全域性變數引起的記憶體洩漏(比如在函式內沒有使用var關鍵字宣告的變數或者使用this宣告的變數)。

    原因:全域性變數,不會被回收。

    解決:使用嚴格模式(‘use strict’)避免。

'use strict'
function test1(){
	global_variable1='我是全域性變數';//這個變數的作用域是window
	console.log(1)
}
test1()//使用嚴格模式報錯:Uncaught ReferenceError: global_variable1 is not defined
function test2(){
	this.global_variable2='我是this建立的變數';
	console.log(2)
}
test2()//使用嚴格模式報錯:Uncaught TypeError: Cannot set property 'global_variable2' of undefined
  1. 閉包引起的記憶體洩漏

    原因:閉包可以讓函式內的區域性變數不被垃圾回收機制回收。

    解決:將事件處理函式定義在外部,解除閉包,或者在定義事件處理函式的外部函式中,刪除對dom的引用。

var testObj1={};
setInterval(function (){
	var testObj2=testObj1;
	var fn=function (){//未使用,但是引用了testObj1,所以為被回收,如果testObj1中的變數是大資料時的,則cpu記憶體會快速增加,可以使用谷歌的performance檢測(不到10秒中,就奔潰了)
		if(typeof testObj2 =='object'){
			console.log('testObj2是物件')
		}
	};
	testObj1={
		name:new Array(100000000).join(','),
		operation:function(){
			console.log('我在飄啊飄')
		}
	};
},1000);


\3. 定時器未清除

原因:定時器中有dom的引用,即使dom刪除了,但是定時器還在,所以記憶體中還是有這個dom。

解決:清除定時器

<ul id='ul'>
	<li>
		<a href="">a標籤1</a>
	</li>
	<li>
		<a href="">a標籤2</a>
	</li>
	<li>
		<a href="">a標籤3</a>
	</li>
</ul>
1234567891011
var timer =setInterval(function(){
	$('#ul').empty();//刪除ul中的子元素
	window.location.reload();//重新整理當前頁面
	console.log($('li'));//當沒有清除定時器時會打印出li的元素集(因為是在定時器中列印,所以每個3秒閃爍一次),當有使用clearInterval清除定時器時在不會打印出內容。
},3000);

轉自:https://blog.csdn.net/weixin_38233549/article/details/89001742