溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Javascript中作用域的原理是什么

發(fā)布時(shí)間:2021-07-06 16:47:33 來(lái)源:億速云 閱讀:166 作者:Leah 欄目:開發(fā)技術(shù)

本篇文章給大家分享的是有關(guān)Javascript中作用域的原理是什么,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來(lái)看看吧。

首先看一個(gè)例子:

var name = 'laruence';
function echo() {
	alert(name);
	var name = 'eve';
	alert(name);
	alert(age);
}

echo();

運(yùn)行結(jié)果是什么呢?

上面的問題, 我相信會(huì)有很多人會(huì)認(rèn)為是:

laruence
eve
[腳本出錯(cuò)]

因?yàn)闀?huì)以為在echo中, 第一次alert的時(shí)候, 會(huì)取到全局變量name的值, 而第二次值被局部變量name覆蓋, 所以第二次alert是’eve’. 而age屬性沒有定義, 所以腳本會(huì)出錯(cuò).

但其實(shí), 運(yùn)行結(jié)果應(yīng)該是:

undefined
eve
[腳本出錯(cuò)]

為什么呢?

JavaScript的作用域鏈

首先讓讓我們來(lái)看看Javasript(簡(jiǎn)稱JS, 不完全代表JScript)的作用域的原理: JS權(quán)威指南中有一句很精辟的描述: ”JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里.” 

為了接下來(lái)的知識(shí), 你能順利理解, 我再提醒一下, 在JS中:”一切皆是對(duì)象, 函數(shù)也是”.

在JS中,作用域的概念和其他語(yǔ)言差不多, 在每次調(diào)用一個(gè)函數(shù)的時(shí)候 ,就會(huì)進(jìn)入一個(gè)函數(shù)內(nèi)的作用域,當(dāng)從函數(shù)返回以后,就返回調(diào)用前的作用域.

JS的語(yǔ)法風(fēng)格和C/C++類似, 但作用域的實(shí)現(xiàn)卻和C/C++不同,并非用“堆棧”方式,而是使用列表,具體過程如下(ECMA262中所述):
任何執(zhí)行上下文時(shí)刻的作用域, 都是由作用域鏈(scope chain, 后面介紹)來(lái)實(shí)現(xiàn).
在一個(gè)函數(shù)被定義的時(shí)候, 會(huì)將它定義時(shí)刻的scope chain鏈接到這個(gè)函數(shù)對(duì)象的[[scope]]屬性.
在一個(gè)函數(shù)對(duì)象被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象(也就是一個(gè)對(duì)象), 然后對(duì)于每一個(gè)函數(shù)的形參,都命名為該活動(dòng)對(duì)象的命名屬性, 然后將這個(gè)活動(dòng)對(duì)象做為此時(shí)的作用域鏈(scope chain)最前端, 并將這個(gè)函數(shù)對(duì)象的[[scope]]加入到scope chain中.

看個(gè)例子:

	var func = function(lps, rps){
		var name = 'laruence';
		........
	}
	func();

在執(zhí)行func的定義語(yǔ)句的時(shí)候, 會(huì)創(chuàng)建一個(gè)這個(gè)函數(shù)對(duì)象的[[scope]]屬性(內(nèi)部屬性,只有JS引擎可以訪問, 但FireFox的幾個(gè)引擎(SpiderMonkey和Rhino)提供了私有屬性__parent__來(lái)訪問它), 并將這個(gè)[[scope]]屬性, 鏈接到定義它的作用域鏈上(后面會(huì)詳細(xì)介紹), 此時(shí)因?yàn)閒unc定義在全局環(huán)境, 所以此時(shí)的[[scope]]只是指向全局活動(dòng)對(duì)象window
active object.

在調(diào)用func的時(shí)候, 會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象(假設(shè)為aObj, 由JS引擎預(yù)編譯時(shí)刻創(chuàng)建, 后面會(huì)介紹),并創(chuàng)建arguments屬性, 然后會(huì)給這個(gè)對(duì)象添加倆個(gè)命名屬性aObj.lps, aObj.rps; 對(duì)于每一個(gè)在這個(gè)函數(shù)中申明的局部變量和函數(shù)定義, 都作為該活動(dòng)對(duì)象的同名命名屬性.

然后將調(diào)用參數(shù)賦值給形參數(shù),對(duì)于缺少的調(diào)用參數(shù),賦值為undefined。

然后將這個(gè)活動(dòng)對(duì)象做為scope chain的最前端, 并將func的[[scope]]屬性所指向的,定義func時(shí)候的頂級(jí)活動(dòng)對(duì)象, 加入到scope china.

有了上面的作用域鏈, 在發(fā)生標(biāo)識(shí)符解析的時(shí)候, 就會(huì)逆向查詢當(dāng)前scope chain列表的每一個(gè)活動(dòng)對(duì)象的屬性,如果找到同名的就返回。找不到,那就是這個(gè)標(biāo)識(shí)符沒有被定義。

注意到, 因?yàn)楹瘮?shù)對(duì)象的[[scope]]屬性是在定義一個(gè)函數(shù)的時(shí)候決定的, 而非調(diào)用的時(shí)候, 所以如下面的例子:

	var name = 'laruence';
	function echo() {
		alert(name);
	}

	function env() {
		var name = 'eve';
		echo();
	}

	env();

運(yùn)行結(jié)果是:

laruence

結(jié)合上面的知識(shí), 我們來(lái)看看下面這個(gè)例子:

function factory() {
	var name = 'laruence';
	var intro = function(){
		alert('I am ' + name);
	}
	return intro;
}

function app(para){
	var name = para;
	var func = factory();
	func();
}

app('eve');

當(dāng)調(diào)用app的時(shí)候, scope chain是由: {window活動(dòng)對(duì)象(全局)}->{app的活動(dòng)對(duì)象} 組成.

在剛進(jìn)入app函數(shù)體時(shí), app的活動(dòng)對(duì)象有一個(gè)arguments屬性, 倆個(gè)值為undefined的屬性: name和func. 和一個(gè)值為’eve’的屬性para;

此時(shí)的scope chain如下:

[[scope chain]] = [
{
	para : 'eve',
	name : undefined,
	func : undefined,
	arguments : []
}, {
	window call object
}
]

當(dāng)調(diào)用進(jìn)入factory的函數(shù)體的時(shí)候, 此時(shí)的factory的scope chain為:

[[scope chain]] = [
{
	name : undefined,
	intor : undefined
}, {
	window call object
}
]

注意到, 此時(shí)的作用域鏈中, 并不包含app的活動(dòng)對(duì)象.

在定義intro函數(shù)的時(shí)候, intro函數(shù)的[[scope]]為:

[[scope chain]] = [
{
	name : 'laruence',
	intor : undefined
}, {
	window call object
}
]

從factory函數(shù)返回以后,在app體內(nèi)調(diào)用intor的時(shí)候, 發(fā)生了標(biāo)識(shí)符解析, 而此時(shí)的sope chain是:

[[scope chain]] = [
{
	intro call object
}, {
	name : 'laruence',
	intor : undefined
}, {
	window call object
}
]

因?yàn)閟cope chain中,并不包含factory活動(dòng)對(duì)象. 所以, name標(biāo)識(shí)符解析的結(jié)果應(yīng)該是factory活動(dòng)對(duì)象中的name屬性, 也就是’laruence’.

所以運(yùn)行結(jié)果是:

I am laruence

現(xiàn)在, 大家對(duì)”JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里.”這句話, 應(yīng)該有了個(gè)全面的認(rèn)識(shí)了吧?

Javascript的預(yù)編譯

我們都知道,JS是一種腳本語(yǔ)言, JS的執(zhí)行過程, 是一種翻譯執(zhí)行的過程.
那么JS的執(zhí)行中, 有沒有類似編譯的過程呢?

首先, 我們來(lái)看一個(gè)例子:

	<script>
	alert(typeof eve); //function
		function eve() {
			alert('I am Laruence');
		};
	</script>

誒? 在alert的時(shí)候, eve不是應(yīng)該還是未定義的么? 怎么eve的類型還是function呢?

恩, 對(duì), 在JS中, 是有預(yù)編譯的過程的, JS在執(zhí)行每一段JS代碼之前, 都會(huì)首先處理var關(guān)鍵字和function定義式(函數(shù)定義式和函數(shù)表達(dá)式).
如上文所說, 在調(diào)用函數(shù)執(zhí)行之前, 會(huì)首先創(chuàng)建一個(gè)活動(dòng)對(duì)象, 然后搜尋這個(gè)函數(shù)中的局部變量定義,和函數(shù)定義, 將變量名和函數(shù)名都做為這個(gè)活動(dòng)對(duì)象的同名屬性, 對(duì)于局部變量定義,變量的值會(huì)在真正執(zhí)行的時(shí)候才計(jì)算, 此時(shí)只是簡(jiǎn)單的賦為undefined.

而對(duì)于函數(shù)的定義,是一個(gè)要注意的地方:

<script>
	alert(typeof eve); //結(jié)果:function
	alert(typeof walle); //結(jié)果:undefined
	function eve() { //函數(shù)定義式
		alert('I am Laruence');
	};
	var walle = function() { //函數(shù)表達(dá)式
	}
	alert(typeof walle); //結(jié)果:function
</script>

這就是函數(shù)定義式和函數(shù)表達(dá)式的不同, 對(duì)于函數(shù)定義式, 會(huì)將函數(shù)定義提前. 而函數(shù)表達(dá)式, 會(huì)在執(zhí)行過程中才計(jì)算.

說到這里, 順便說一個(gè)問題 :

	var name = 'laruence';
	age = 26;

我們都知道不使用var關(guān)鍵字定義的變量, 相當(dāng)于是全局變量, 聯(lián)系到我們剛才的知識(shí):

在對(duì)age做標(biāo)識(shí)符解析的時(shí)候, 因?yàn)槭菍懖僮? 所以當(dāng)找到到全局的window活動(dòng)對(duì)象的時(shí)候都沒有找到這個(gè)標(biāo)識(shí)符的時(shí)候, 會(huì)在window活動(dòng)對(duì)象的基礎(chǔ)上, 返回一個(gè)值為undefined的age屬性.

也就是說, age會(huì)被定義在頂級(jí)作用域中.

現(xiàn)在, 也許你注意到了我剛才說的: JS在執(zhí)行每一段JS代碼..
對(duì), 讓我們看看下面的例子:

<script>
	alert(typeof eve); //結(jié)果:undefined
</script>
<script>
	function eve() {
		alert('I am Laruence');
	}
</script>

明白了么? 也就是JS的預(yù)編譯是以段為處理單元的…

揭開謎底

現(xiàn)在讓我們回到我們的第一個(gè)問題:

當(dāng)echo函數(shù)被調(diào)用的時(shí)候, echo的活動(dòng)對(duì)象已經(jīng)被預(yù)編譯過程創(chuàng)建, 此時(shí)echo的活動(dòng)對(duì)象為:

[callObj] = {
name : undefined
}

以上就是Javascript中作用域的原理是什么,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI