執(zhí)行環(huán)境
- 執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了他們各自的行為。每個(gè)執(zhí)行環(huán)境都有一個(gè)變量對象。解析器在處理數(shù)據(jù)的時(shí)候,會在后臺使用它。在所有的代碼執(zhí)行完畢后,執(zhí)行環(huán)境就會被銷毀。
- 全局執(zhí)行環(huán)境是最大的一個(gè)執(zhí)行環(huán)境,在web瀏覽器中,window被認(rèn)為是最大的執(zhí)行環(huán)境,所有的變量和函數(shù)都是作為window對象的屬性和方法存在的。當(dāng)應(yīng)用程序退出(關(guān)閉網(wǎng)頁或者瀏覽器),window就會被銷毀。
- 每個(gè)函數(shù)也都有自己的執(zhí)行環(huán)境,當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會被推入到一個(gè)環(huán)境棧中,而在函數(shù)執(zhí)行完之后,棧會將其環(huán)境彈出,恢復(fù)到上一個(gè)執(zhí)行環(huán)境中。
作用域鏈(scope chain)
- 當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會創(chuàng)建變量對象的一個(gè)作用域鏈。用途:保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。
- 作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對象,如果這個(gè)環(huán)境是函數(shù),則將其活動對象(activation object)作為變量對象,活動對象最開始的時(shí)候只包含一個(gè)變量,即arguments對象(這個(gè)對象在全局環(huán)境中是不存在的)。全局執(zhí)行環(huán)境中的變量始終都是作用域鏈中的最后一個(gè)對象。
- 標(biāo)識符的解析是沿著作用域鏈一級一級的搜索標(biāo)識符的過程。
- 內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,但外部環(huán)境不能訪問內(nèi)部環(huán)境中的任何變量和函數(shù)。
var color = 'red';
function changeColr(){
var another = 'blue';
function swapColpr() {
var temp = 'yellow';
console.log(color); //red 可以訪問祖先執(zhí)行環(huán)境
console.log(another); //blue 可以訪問父執(zhí)行環(huán)境
console.log(temp); //yellow 可以訪問自身執(zhí)行環(huán)境
}
swapColpr();
console.log('------------------');
console.log(color); //red 可以訪問父執(zhí)行環(huán)境
console.log(another); //blue 可以訪問自身執(zhí)行環(huán)境
console.log(temp); // error not defined 不能訪問子執(zhí)行環(huán)境
}
console.log('------------------');
console.log(color); //red 可以訪問自身執(zhí)行環(huán)境
console.log(another); //error not defined 不能訪問子執(zhí)行環(huán)境
console.log(temp); //error not defined 不能訪問子執(zhí)行環(huán)境
changeColr();
延長作用域鏈
- 原因:有些語句可以在作用域鏈的前端臨時(shí)增加一個(gè)變量,該變量對象會在代碼執(zhí)行后被移除。所以通過此方法可以延長作用域鏈。
- 方法:當(dāng)執(zhí)行流進(jìn)入到下列任何一個(gè)語句時(shí),作用域鏈就會被延長
-
try-catch語句的catch塊。會創(chuàng)建一個(gè)新的變量,其中包含拋出錯(cuò)誤對象的聲明。 -
with語句。會將指定對象添加到作用域鏈中。
function bulidUrl(){
var qs = "debug = true";
with(location){
var url = href + qs;
}
return url
}
var result = bulidUrl();
console.log(result);

以上代碼假設(shè)with不能延長作用域鏈的話,那么url就是with函數(shù)內(nèi)的局部變量,他的父執(zhí)行環(huán)境bulidUrl是無法訪問的,那么return url就會報(bào)錯(cuò),但是with可以延長作用域鏈,他接收到的是location對象,因此其變量對象中就包含了location對象所有的屬性和方法,并且把這個(gè)變量對象添加到作用域鏈的前端,當(dāng)在with語句中引用變量href時(shí)(實(shí)際上引用的是location.href),可以在當(dāng)前執(zhí)行環(huán)境的變量對象中找到,當(dāng)變量引用qs時(shí),引用的是在bulidUrl()中定義的那個(gè)變量,至于with語句內(nèi)部,則定義了一個(gè)名為url的變量,因此url就成了函數(shù)執(zhí)行環(huán)境的一部分,可以作為函數(shù)值被返回。
沒有塊級作用域
if(true){
var color = "blue";
}
console.log(color)

在其他強(qiáng)類型語言,例如C語言中,color是定義在if語句內(nèi)的變量,if是具有塊級作用域的,當(dāng)if執(zhí)行完,color就會被銷毀,所以最后輸出undefined,但是在JavaScript語言中,if語句中的變量聲明會將變量添加到當(dāng)前的執(zhí)行環(huán)境(此時(shí)當(dāng)前的執(zhí)行環(huán)境就是全局執(zhí)行環(huán)境)中。for語句也是這樣的道理。
垃圾收集
JavaScript具有自動垃圾收集機(jī)制,執(zhí)行環(huán)境會負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存,所需內(nèi)存的分配以及無用內(nèi)存的回收完全實(shí)現(xiàn)了自動化管理。
原理:垃圾回收器按照固定的時(shí)間間隔找出那些不在繼續(xù)使用的變量,然后釋放其占用的內(nèi)存。
- 實(shí)現(xiàn)方式:
- 標(biāo)記清除
先給即將使用的變量標(biāo)記---清除標(biāo)記---標(biāo)記使用后的變量,便于刪除 - 引用計(jì)數(shù):跟蹤標(biāo)記記錄每個(gè)值被引用的次數(shù)。
當(dāng)聲明了一個(gè)變量a并且將一個(gè)引用類型的值Q賦值給a,Q的引用就+1,如果Q又被b引用,Q的引用在+1,此時(shí)為2。當(dāng)a不引用Q而引用T的時(shí)候,Q的引用就-1,此時(shí)Q的引用為1,當(dāng)Q的引用為0時(shí),就將Q占用的內(nèi)存空間收回來。 - 引用計(jì)數(shù)的缺點(diǎn):循環(huán)引用
function fn(){
var obj1 = new Object();
var obj2 = new Object();
obj1.someOtherObject = obj2;
obj2.anotherObject = obj1;
}
例如以上代碼,obj1通過someOtherObject引用obj2,obj2通過anotherObject引用obj1,那么obj1和obj2的引用都是2,永遠(yuǎn)不會變成0,所以使用引用計(jì)數(shù)永遠(yuǎn)無法回收,假設(shè)這個(gè)函數(shù)被重復(fù)多調(diào)用,那么就會導(dǎo)致大量的內(nèi)存得不到回收。
在IE瀏覽器中,為了避免DOM對象和JavaScript原生對象之間循環(huán)引用問題,最好在不使用它們的時(shí)候,手動斷開它們之間的聯(lián)系。
myObject.element = null;
element.somObject = null;
設(shè)置為null就斷開了它們之間的聯(lián)系,垃圾回收機(jī)制就會對他們進(jìn)行內(nèi)存回收。
原代碼:
/*原生對象與DOM對象是循環(huán)使用的*/
var element = document.getElementById('some_element'); //DOM對象
var myObject = new Objrct(); //JavaScript原生對象
myObject.element = element; // 原聲對象與DOM對象的聯(lián)系
element.someObject = myObject; // DOM對象與原聲對象的聯(lián)系
為了解決上訴問題,IE9把DOM和BOM對象都轉(zhuǎn)成了真正的JavaScript對象。
- 性能問題
垃圾收集器是周期性運(yùn)行的,確定垃圾收集的時(shí)間間隔是一個(gè)非常重要的問題,JavaScript垃圾回收機(jī)制:觸發(fā)垃圾收集的變量分配,字面量和數(shù)組元素的臨界值是動態(tài)修正的。
在IE中調(diào)用window.ClooectGrabage()方法會立即執(zhí)行垃圾收集。
在Opera7 + 版本中,調(diào)用window.opera.collect()會啟動垃圾回收機(jī)制。
管理內(nèi)存
分配給web瀏覽器的可用內(nèi)存數(shù)量通常要比分配給桌面應(yīng)用程序的少,目的:防止運(yùn)行JavaScript的網(wǎng)頁耗盡全部系統(tǒng)內(nèi)存造成系統(tǒng)崩潰。內(nèi)存限制的問題不僅會影響給變量分配內(nèi)存,同時(shí)還會影響調(diào)用棧以及一個(gè)在線程中能夠同時(shí)執(zhí)行的語句數(shù)量。
- 優(yōu)化內(nèi)存的方法:解除引用
為執(zhí)行中的代碼保存必要的數(shù)據(jù),一旦數(shù)據(jù)不使用,通過將其值設(shè)置為null來釋放其引用。
解除引用不意味著自動回收該值所占用的內(nèi)存,解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便垃圾收集器下次運(yùn)行的時(shí)候?qū)⑵涫栈亍?/strong>
function fnname){
var person = new Object();
person.name = name;
return person; //person為局部變量,當(dāng)函數(shù)執(zhí)行完畢后,自動離開執(zhí)行環(huán)境,不需要設(shè)置null。
}
var resut = fn('liqi');
resut = null //手動解決resut的引用
以上內(nèi)容小結(jié):
JavaScript變量可以用來保存兩種類型的值:基本類型和引用類型?;绢愋偷闹蛋?種:undefined,null。number,Boolean,string?;绢愋团c引用類型具有以下特點(diǎn):
- 基本類型的值在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中;
- 從一個(gè)變量向另外一個(gè)變量copy基本類型的值,會創(chuàng)建這個(gè)值的一個(gè)副本;
- 引用類型的值是一個(gè)對象,保存在堆內(nèi)存中;
- 包含引用類型值的變量實(shí)際上包含的并不是對象本身,而是一個(gè)指向該對象的指針;
- 從一個(gè)變量向另一個(gè)變量copy引用類型的值,實(shí)際上copy的是指針,因此兩個(gè)變量最終指向同一個(gè)對象;
- 確定一個(gè)值是哪種基本類型可以使用
typeof操作符,而確定一個(gè)值是哪種引用類型可以使用instanceof操作符。
所有變量都存在與一個(gè)執(zhí)行環(huán)境中(也成為作用域),這個(gè)執(zhí)行環(huán)境決定了變量的聲明周期,以及那一部分代碼可以訪問其中的變量,以下是關(guān)于執(zhí)行環(huán)境的總結(jié): - 執(zhí)行環(huán)境有全局執(zhí)行環(huán)境(也成為全局環(huán)境)和函數(shù)執(zhí)行環(huán)境之分;
- 每次進(jìn)入一個(gè)新的執(zhí)行環(huán)境,都會創(chuàng)建一個(gè)用于搜索變量和函數(shù)的作用域鏈;
- 函數(shù)的局部環(huán)境不僅有權(quán)訪問函數(shù)作用域中的變量,還可以訪問其父環(huán)境以及全局環(huán)境;
- 全局環(huán)境只能訪問在全局下定義的變量和函數(shù),不能訪問局部環(huán)境中的;
- 變量的執(zhí)行環(huán)境有助于確定如何釋放內(nèi)存。
JavaScript是一門具有自動垃圾回收機(jī)制的編程語言,開發(fā)人員不必關(guān)心內(nèi)存分配和回收的問題,垃圾回收機(jī)制總結(jié): - 離開作用域的值將自動標(biāo)記為可以回收,因此在垃圾收集期間被刪除;
- 標(biāo)記清除是目前主流的垃圾回收法,這種算法的思想是給當(dāng)前不使用的值加上標(biāo)記,然后在回收其內(nèi)存;
- 另一種垃圾回收算法是引用計(jì)數(shù),這種算法的思想是跟蹤記錄所有值被引用的次數(shù)。JavaScript引擎目前都不在使用這種算法,但在IE瀏覽器中訪問非原生JavaScript對象時(shí),這種算法仍然可能導(dǎo)致問題;
- 當(dāng)代碼中存在循環(huán)引用時(shí),引用計(jì)數(shù)法就會存在問題;
- 解除變量的引用不僅有助于消除循環(huán)引用現(xiàn)象,而且對于垃圾收集也有好處,為了確保有效的回收內(nèi)存,應(yīng)該及時(shí)解除不在使用的全局對象,全局對象屬性以及循環(huán)引用變量的引用。
代碼解析示例1
- 代碼解析示例
var x = 10
bar()
function foo() {
console.log(x)
}
function bar(){
var x = 30
foo() // 輸出什么
}
當(dāng)聲明的時(shí)候,會出現(xiàn)下面的情況
globalContext = {
AO: {
x: 10
foo: function
bar: function
},
Scope: null
}
//聲明 foo 時(shí) 得到下面
foo.[[scope]] = globalContext.AO
//聲明 bar 時(shí) 得到下面
bar.[[scope]] = globalContext.AO
注意: 在當(dāng)前的執(zhí)行上下文內(nèi)聲明的函數(shù),這個(gè)函數(shù)的[[scope]]就執(zhí)行當(dāng)前執(zhí)行上下文的 AO
當(dāng)調(diào)用 bar() 時(shí), 進(jìn)入 bar 的執(zhí)行上下文
barContext = {
AO: {
x: 30
},
Scope: bar.[[scope]] //globalContext.AO
}
當(dāng)調(diào)用 foo() 時(shí),先從 bar 執(zhí)行上下文中的 AO里找,找不到再從 bar 的 [[scope]]里找,找到后即調(diào)用
當(dāng)調(diào)用 foo() 時(shí),進(jìn)入 foo 的執(zhí)行上下文
fooContext = {
AO: {},
Scope: foo.[[scope]] // globalContext.AO
}
所以 console.log(x)是 10
代碼解析示例2
- 代碼解析示例
var x = 10;
bar() // 輸出什么
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
進(jìn)行聲明的時(shí)候,會出現(xiàn)
globalContext = {
AO: {
x: 10
bar: function
},
Scope: null
}
//聲明 bar 時(shí) 得到下面
bar.[[scope]] = globalContext.AO
注意: 在當(dāng)前的執(zhí)行上下文內(nèi)聲明的函數(shù),這個(gè)函數(shù)的[[scope]]就執(zhí)行當(dāng)前執(zhí)行上下文的 AO
當(dāng)調(diào)用 bar() 時(shí), 進(jìn)入 bar 的執(zhí)行上下文
barContext = {
AO: {
x: 30,
foo: function
},
Scope: bar.[[scope]] //globalContext.AO
}
//在 bar 的執(zhí)行上下文里聲明 foo 時(shí) 得到下面
foo.[[scope]] = barContext.AO
當(dāng)調(diào)用 foo() 時(shí),先從 bar 執(zhí)行上下文中的 AO里找,找到后即調(diào)用
當(dāng)調(diào)用 foo() 時(shí),進(jìn)入 foo 的執(zhí)行上下文
fooContext = {
AO: {},
Scope: foo.[[scope]] // barContext.AO
}
所以 console.log(x)是 30
相關(guān)概念
- 執(zhí)行上下文 executionContext
- 活動對象 AO
- Scope 屬性
- 執(zhí)行順序
筆試題
var a = 1;
function fn(){
console.log(a); //undefined
var a = 5;
console.log(a); // 5
a++; //a = 6
var a; // a= 6
fn3();
fn2();
console.log(a); // 20
function fn2(){
console.log(a); //6
a = 20;
}
}
function fn3(){
console.log(a) //a = 1
a = 200;
}
fn();
console.log(a); //200
補(bǔ)充說明
- JS普通變量的作用域是詞法作用域:詞法分析,不需要執(zhí)行就可以知道是什么。
- 詞法作用域只能確定他是哪一個(gè),不能確定他具體的取值。
-
this不是詞法作用域,必須調(diào)用才能確定this。 - 就近原則,是作用域進(jìn),不是代碼行數(shù)少。