首頁 web前端 js教程 javascript執行環境及作用域詳解_javascript技巧

javascript執行環境及作用域詳解_javascript技巧

May 16, 2016 pm 03:02 PM
javascript 作用域 執行環境

最近在重讀《javascript高級程式設計3》,覺得應該寫一些部落格記錄學習的一些知識,不然都忘光啦。今天要總結的是js執行環境和作用域。

先來先來看看執行環境 

一、執行環境
        書上概念,而執行環境定義了變數或函數有權存取的其他數據,決定了他們各自的行為。每個執行環境都有一個與之關聯的變數物件。環境中定義的所有變數和函數都保存在這個物件中。雖然我們在編寫程式碼的時候無法存取這個對象,但解析器在處理資料時會在後台使用到它。

  執行環境是一個概念,一種機制,它定義了變數或函數是否有權存取其他資料

 在javascript中,可執行的JavaScript程式碼分成三種:
       1. Global Code,即全域的、不在任何函數裡面的程式碼,例如:一個js檔案、嵌入在HTML頁面中的js程式碼等。
       2. Eval Code,即使用eval()函數動態執行的JS程式碼。
       3. Function Code,即使用者自訂函數中的函數體JS程式碼。

跳過Eval Code,只說全域執行環境和函數執行環境。 

1、全域環境:

  全域環境是最外圍的一個執行環境。全域執行環境被認為是window物件。因此所有全域變數和函數都是作為window物件的屬性和方法建立的。程式碼載入瀏覽器時,全域執行環境被建立(當我們關閉網頁或瀏覽器時全域執行環境才被銷毀)。例如在一個頁面中,第一次載入JS程式碼時建立一個全域執行環境。

  這也是為什麼閉包有一個記憶體外洩的缺點。因為閉包中外部函數被當成了全域環境。所以不會被銷毀,一直保存在記憶體中。

2、函數執行環境

  每個函數都有自己的執行環境,當執行進入一個函數時,函數的執行環境就會被推入一個執行環境堆疊的頂部並取得執行權。當這個函數執行完畢,它的執行環境又從這個堆疊的頂端被刪除,並且把執行權並還給之前執行環境。這就是ECMAScript程式中的執行流程。

  也可以這樣解讀:當呼叫一個 JavaScript 函數時,函數就會進入與該函數相對應的執行環境。如果又呼叫了另一個函數,則會建立一個新的執行環境,並且在函數呼叫期間執行過程都處於該環境中。當呼叫的函數傳回後,執行過程會傳回原始執行環境。因而,運行中的 JavaScript 程式碼就構成了一個執行環境堆疊。

  當函數被呼叫時函數的局部環境被創建(函數內的程式碼執行完畢後,該環境被銷毀,同時保存在其中的所有變數和函數定義也隨之被銷毀)。

2-1定義期 

  函數定義的時候,都會創建一個[[scope]]屬性,通這個物件對應的是一個物件的列表,列表中的物件僅能javascript內部訪問,沒法透過語法存取。

  (scope也就是作用域的意思。) 

  我們定義一全域函數A,那麼A函數就建立了一個A的[[scope]]屬性。此時,[[scope]]裡面只包含了全域物件【Global Object】。

  而如果, 我們在A的內部定義一個B函數,那麼B函數同樣會創建一個[[scope]]屬性,B的[[scope]]屬性包含了兩個對象,一個是A的活動對象Activation Object、一個是全域對象,A的活動對像在前面,全域物件排在後面。

  簡而言之,一個函數的[Scope]屬性中物件列表的順序是上一層函數的Activation Object對象,然後是上上層的,一直到最外層的全域物件。  

下面是範例程式碼:A只有一個scope,B有兩個scope

// 外部函数
function A(){
  var somevar;
  
  // 内部函数
 function B(){
   var somevar;
  }
}
登入後複製

2-2執行期 

  當函數被執行的時候,就是進入這個函數的執行環境,首先會創一個它自己的活動物件【Activation Object】(這個物件中包含了this、參數(arguments)、局部變數(包括命名的參數)的定義和一個變數物件的作用域鏈[[scope chain]],然後,把這個執行環境的[scope]按順序複製到[[scope chain]]裡,最後把這個活動物件推入到[ [scope chain]]的頂部。

// 第一步页面载入创全局执行环境global executing context和全局活动象
// 定义全局[[scope]],只含有Window对象
// 扫描全局的定义变量及函数对象:color【undefined】、changecolor【FD创建changecolor的[[scope]],此时里面只含有全局活动对象】,加入到window中,所以全局变量和全局函数对象都是做为window的属性定义的。
// 程序已经定义好所以在此执行环境内任何位置都可以执行changecolor(),color也已经被定义,但是它的值是undefined

// 第二步color赋值"blue"
var color = "blue";

// 它是不需要赋值的,它就是引用本身
function changecolor() {
 // 第四步进入changecolor的执行环境
 // 复制changecolor的[[scope]]到scope chain
 // 创建活动对象,扫描定义变量和定义函数,anothercolor【undefined】和swapcolors【FD创建swapcolors的[[scope]]加入changecolor的活动对象和全局活动对象】加入到活动对象,活动对象中同时还要加入arguments和this
 // 活动对象推入scope chain 顶端
 // 程序已经定义好所以在此执行环境内任何位置都可以执行swapcolors(),anothercolor也已经被定义好,但它的值是undefined
 
 // 第五anothercolor赋值"red"
 var anothercolor = "red";
 
 // 它是不需要赋值的,它就是引用本身
 function swapcolors() {
  // 第七步进入swapcolors的执行环境,创建它的活动对象
  // 复制swapcolors的[[scope]]到scope chain
  // 扫描定义变量和定义函数对象,活动对象中加入变量tempcolor【undefined】以及arguments和this
  // 活动对象推入scope chain 顶端
  
  // 第八步tempcolor赋值anothercolor,anothercolor和color会沿着scope chain被查到,并继续往下执行
  var tempcolor = anothercolor;
   anothercolor = color;
   color = tempcolor; 
 }

 // 第六步执行swapcolors,进入其执行环境
 swapcolors();
}

// 第三步执行changecolor,进入其执行环境
changecolor();

登入後複製

2-3访问标识符:

  当执行js代码的过程中,遇到一个标识符,就会根据标识符的名称,在执行上下文(Execution Context)的作用域链中进行搜索。从作用域链的第一个对象(该函数的Activation Object对象)开始,如果没有找到,就搜索作用域链中的下一个对象,如此往复,直到找到了标识符的定义。如果在搜索完作用域中的最后一个对象,也就是全局对象(Global Object)以后也没有找到,则会抛出一个错误,提示undefined。

二、Scope/Scope Chain(作用域/作用域链)

  当代码在一个环境中执行时,都会创建一个作用域链。 作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。整个作用域链是由不同执行位置上的变量对象按照规则所构建一个链表。作用域链的最前端,始终是当前正在执行的代码所在环境的变量对象。

  如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,就是函数内部的arguments对象。作用域链中的下一个变量对象来自该函数的包含环境,而再下一个变量对象来自再下一个包含环境。这样,一直延续到全局执行环境,全局执行环境的Variable Object始终是作用域链中的最后一个对象。

如图所示:

书中例子:

 var color="blue";
 function changecolor(){
 var anothercolor="red";
 function swapcolors(){
 var tempcolor=anothercolor;
 anothercolor=color;
 color=tempcolor;
  // Todo something  
  }
 swapcolors();
}
changecolor();
 //这里不能访问tempcolor和anocolor;但是可以访问color;
alert("Color is now "+color);
登入後複製

  通过上面的分析,我们可以得知内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。

  这些环境之间是线性、有次序的。每个环境都可以向上搜索作用域链,以便查询变量和函数名;但任何环境不能通过向下搜索作用域链条而进入另一个执行环境。

  对于上述例子的swapcolor()函数而言,其作用域链包括:swapcolor()的变量对象、changecolor()变量对象和全局对象。swapcolor()的局部环境开始先在自己的Variable Object中搜索变量和函数名,找不到,则向上搜索changecolor作用域链。。。。。以此类推。但是,changecolor()函数是无法访问swapcolor中的变量

启示:尽量使用局部变量,能够减少搜索的时间

1、没有块级作用域

与C、C++以及JAVA不同,Javscript没有块级作用域。看下面代码:

if(true){
  var myvar = "张三"; 
 }
 alert(myvar);// 张三
登入後複製

如果有块级作用域,外部是访问不到myvar的。再看下面

for (var i=0;i<10;i++){
   console.log(i) 
  }
  
  alert(i); // 10
登入後複製

对于有块级作用域的语言来说,比如java或是c#代码,i做为for初始化的变量,在for之外是访问不到的。因为i只存在于for循环体重,在运行完for循环后,for中的所有变量就被销毁了。而在javascript中则不是这样的,在for中的变量声明将会添加到当前的执行环境中(这里是全局执行环境),因此在for循环完后,变量i依旧存在于循环外部的执行环境。因此,会输出10。

2、声明变量

使用var声明变量时,这个变量将被自动添加到距离最近的可用环境中。对于函数内部,最接近的环境就是函数的局部变量。如果初始化变量时没有使用var,该变量会自动添加到全局函数中。

代码如下:

var name = "小明";
function getName(){
 alert( name ); //'undefined'
 var name = '小黄';
 alert(name ); //小黄
}
getName()
登入後複製

为什么第一个name是undefined呢。这是因为,javascript解析器,进入一个函数执行环境,先对var 和 function进行扫描。

相当于会把var或者function【函数声明】声明提升到执行环境顶部。

也就是说,进入我们的getName函数的时候,标识符查找机制查找到了var,查找的name是局部变量name,而不是全局的name,因为函数里面的name被提升到了顶部。

上面的代码会被解析成下面这样:

var name = "小明";
function getName(){
 var name;
 alert( name ); //'undefined'
 var name = '小黄';
 alert(name ); //小黄
}
getName()
登入後複製
 

延长作用域链:

  虽然执行环境只有两种——全局作用域和函数作用域,但是还是可以通过某种方式来延长作用域链。因为有些语句可以在作用域链的顶部增加一个临时的变量对象。
有两种情况会发生这种现象:
1、try-catch语句的catch块;
2、with语句;

以上就是本文的全部内容,希望对大家学习理解javascript执行环境及作用域有所帮助。

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1662
14
CakePHP 教程
1418
52
Laravel 教程
1311
25
PHP教程
1261
29
C# 教程
1234
24
c語言中typedef struct的用法 c語言中typedef struct的用法 May 09, 2024 am 10:15 AM

typedef struct 在 C 語言中用於建立結構體類型別名,簡化結構體使用。它透過指定結構體別名將一個新的資料類型作為現有結構體的別名。優點包括增強可讀性、程式碼重複使用和類型檢查。注意:在使用別名前必須定義結構體,別名在程式中必須唯一且僅在其宣告的作用域內有效。

java中的variable expected怎麼解決 java中的variable expected怎麼解決 May 07, 2024 am 02:48 AM

Java 中的變數期望值異常可以透過以下方法解決:初始化變數;使用預設值;使用 null 值;使用檢查和賦值;了解局部變數的作用域。

js中閉包的優缺點 js中閉包的優缺點 May 10, 2024 am 04:39 AM

JavaScript 閉包的優點包括維持變數作用域、實作模組化程式碼、延遲執行和事件處理;缺點包括記憶體洩漏、增加了複雜性、效能開銷和作用域鏈影響。

c++中的include什麼意思 c++中的include什麼意思 May 09, 2024 am 01:45 AM

C++ 中的 #include 預處理器指令將外部來源檔案的內容插入到目前原始檔案中,以複製其內容到目前原始檔案的相應位置。主要用於包含頭文件,這些頭文件包含程式碼中所需的聲明,例如 #include <iostream> 是包含標準輸入/輸出函數。

C++ 智慧指標:全面剖析其生命週期 C++ 智慧指標:全面剖析其生命週期 May 09, 2024 am 11:06 AM

C++智慧指標的生命週期:建立:分配記憶體時建立智慧指標。所有權轉移:透過移動操作轉移所有權。釋放:智慧指標離開作用域或被明確釋放時釋放記憶體。物件銷毀:所指向物件被銷毀時,智慧型指標成為無效指標。

c++中函數的定義和呼叫可以巢狀嗎 c++中函數的定義和呼叫可以巢狀嗎 May 06, 2024 pm 06:36 PM

可以。 C++ 允許函數巢狀定義和呼叫。外部函數可定義內建函數,內部函數可在作用域內直接呼叫。巢狀函數增強了封裝性、可重複用性和作用域控制。但內部函數無法直接存取外部函數的局部變量,且傳回值類型需與外部函數宣告一致,內部函數不能自遞歸。

vue中let和var的區別 vue中let和var的區別 May 08, 2024 pm 04:21 PM

在 Vue 中,let 和 var 宣告變數時在作用域上存在差異:作用域:var 具有全域作用域,let 具有區塊級作用域。區塊級作用域:var 不會建立區塊級作用域,let 建立區塊級作用域。重新宣告:var 允許在同一作用域內重新宣告變數,let 不允許。

C++ 智慧指標:從基礎到高級 C++ 智慧指標:從基礎到高級 May 09, 2024 pm 09:27 PM

智慧指針是C++專用指針,能夠自動釋放堆記憶體對象,避免記憶體錯誤。類型包括:unique_ptr:獨佔所有權,指向單一物件。 shared_ptr:共享所有權,允許多個指標同時管理物件。 weak_ptr:弱引用,不增加引用計數,避免循環引用。使用方法:使用std命名空間的make_unique、make_shared和make_weak建立智慧指標。智慧型指標在作用域結束時自動釋放物件記憶體。進階用法:可以使用自訂刪除器控制物件釋放方式。智慧型指標可有效管理動態數組,防止記憶體洩漏。

See all articles