學習JS第四節——作用域、閉包、立即執行函數

1.函數的隱式屬性

  • [[scope]]:就是常說的作用域,其中存儲了運行期上下文的集合
  • 作用域鏈:[[scope]]中所存儲的執行期上下文對象的集合,這個集合呈鏈式鏈接,將這種鏈式鏈接叫做作用域鏈。

每個JavaScript函數都是一個對象,對象中有些屬性屬性可以訪問,但是有些不可以,這些屬性僅供JavaScript引擎存取,[[scope]]就是其中一個。
運行期上下文:函數每次執行時都會產生一個上下文,這個上下文AO爲即時的,因此執行完就被銷燬。
查找變量:從作用域鏈的頂端依次向下查找。

2.作用域鏈

function a() {
}
var glob = 100;
a();

//a被定義時   a. [[scope]]     --> 0:GO{}
//a被執行時   a. [[scope]]		--> 0:AO{}
											-->1:GO{}

在這裏插入圖片描述
在這裏插入圖片描述

function a() {
	function b() {
		var b = 234;
	}
	var a = 123;
	b();
}
var glob = 100;
a();

//a被定義時   a. [[scope]]     --> 0:GO{}
//a被執行時   a. [[scope]]		--> 0:a.AO{}
											-->1:GO{}
//b被定義時   b. [[scope]]    --> 0:a.AO{}
											-->1:GO{}
//b被執行時   b. [[scope]]		-->0:b.AO{}
											--> 1:a.AO{}
											-->2:GO{}

在這裏插入圖片描述
在這裏插入圖片描述

3.閉包的產生

閉包:當內部函數被保存到外部時,將會生成閉包。閉包會導致原有作用域鏈不釋放,造成內存泄漏。

閉包不是隻有外部函數最後加一個return內部函數語句才能產生
也可以通過設置全局變量等於內部函數產生
總而言之,只要內部函數活得比外部函數長,就可以產生閉包

//全局變量等於內部函數的方法
var demo;
function test() {
	var abc = 100;
	function a() {
		console.log(abc);
	}
	demo = a;
}
test();			//若沒有這個語句則會報錯沒有定義demo函數
					//只有test執行,才能將a函數賦值給全局變量
demo();
//輸出 100

4.閉包的優點:

  • 實現公有變量
    eg:函數累加器(不依賴於外部變量,且能反覆執行,即每次執行結果都不一樣)

獨立的功能不能依賴於外部變量

function add() {
	var num = 0;
	function a() {
		console.log(++num);
	}
	return a;
}

var myAdd = add();
myAdd();
myAdd();
myAdd();

//輸出 1 2 3
  • 可以做緩存
function test() {
	var food = "apple";
	var obj = {
		eatFood : function() {
			if(food != "") {
				console.log("I am eating" + food);
				food = "";
			} else {
				console.log("There is nothing!");
			}
		},
		pushFood : function (myFood) {
			food = myFood;
		}
	}
	return obj;
}

var person = test();

person.eatFood();
person.eatFood();
person.pushFood("banana");
person.eatFood();

//輸出
I am eating apple
There is nothing!
I am eating banana
  • 變量私有化
function Deng(name, wife) {
	//這個變量在函數執行時會在函數的執行上下文中
	var prepareWife = "xiaozhang";

	this.name = name;
	this.wife = wife;
	this.divorce = function() {
		this.wife = prepareWife;
		//注意這個prepareWife前沒有加this
	}
}

var deng = new Deng('deng', 'xiaoliu');

//控制檯輸入 deng.divorce
//再輸入 deng.wife
//輸出 xiaozhang 

//輸入 deng.prepareWife
//返回 undefined
//因爲這個在構造函數中定義的變量不是對象的方法,因此不能訪問
//這樣就形成了私有化變量

在對象方法中能夠直接使用在對象中定義的變量是因爲 new構造函數後,最後一步會返回構造函數對象,因此會形成閉包,就可以使用函數中定義的變量。

  • 模塊化開發,防治污染全局變量(參見學習JS第七節命名空間)

5.立即執行函數

定義:多用於只執行一次的函數,因爲執行一次後立即銷燬,避免佔用過多內存(初始化功能的函數)

//括號的兩種立即執行函數
(function () {
   
}) ()


//多用這種
(function () {
   
} ())

//括號優先級最高,因此先將其轉換成函數表達式,因此兩種方式都對

只有表達式才能被執行符號執行
能被執行符號執行的表達式,會放棄名字
!+ - && ()操作符可以將函數聲明變成表達式

function test() {
	var a = 123;
}()
//報錯,不能執行
//因爲只是函數聲明,不是表達式

var test = function () {
}()
//可以執行
//執行完之後再輸 test()會報錯
//因爲此時立即執行函數已經銷燬,此時test是個空的變量

var test = function() {
}();
//輸入:test
//返回:undefined
//因爲 被執行符號執行的表達式,會放棄自己的名字

+ var test = function() {
}();
//可以執行

真題解析

var x = 1;
if(function f() {}) {
	x += typeof f;
}
console.log(x);

//輸出 1undefined
//括號會將f變成表達式,就不是函數定義了,因此f就沒有被定義

6.立即執行函數與閉包的聯繫

function test() {
	var arr = [];
	for(var i = 0; i < 10; i++) {
		arr[i] = function() {
			console.log(i);
			//arr裏面保存的函數的 [[scope]]包括
			//函數test的AO:{ i=10 }
			//全局的GO
		}
	}
	return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
	myArr[j]();
}

//輸出 10 10 10 10 10 10 10 10 10 10 10

//加上立即執行函數後
function test() {
	var arr = [];
	for(var i = 0; i < 10; i++) {
	    (function (j) {
			arr[j] = function() {
				console.log(j);
			}
			//arr裏面保存的函數的 [[scope]]包括
			//外層立即執行函數的AO:{  形參j=實參i }
			//函數test的AO:{ i=10 }
			//全局的GO
		}(i));
	}
	return arr;
}
var myArr = test();
for(var k = 0; k < 10; k++) {
	myArr[k]();
}
//輸出 0 1 2 3 4 5 6 7 8 9

7.逗號表達式:

var a = (表達式1,表達式2);
//a被賦值爲表達式2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章