javascript执行环境及作用域详解


Posted in Javascript onMay 05, 2016

最近在重读《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]]的顶部。这样[[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始终是作用域链中的最后一个对象。

如图所示:

javascript执行环境及作用域详解

书中例子:

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执行环境及作用域有所帮助。

Javascript 相关文章推荐
正则表达式语法
Oct 09 Javascript
JavaScript中对象property的读取和写入方法介绍
Dec 30 Javascript
canvas学习之API整理笔记(一)
Dec 29 Javascript
JS判断一个数是否是水仙花数
Jun 11 Javascript
Vue-cli 使用json server在本地模拟请求数据的示例代码
Nov 02 Javascript
深入剖析Node.js cluster模块
May 23 Javascript
vue下history模式刷新后404错误解决方法
Aug 18 Javascript
Vue开发之封装上传文件组件与用法示例
Apr 25 Javascript
微信小程序云开发之模拟后台增删改查
May 16 Javascript
在React中写一个Animation组件为组件进入和离开加上动画/过度效果
Jun 24 Javascript
一篇文章带你从零快速上手Rollup
Sep 07 Javascript
Vue和Flask通信的实现
May 19 Vue.js
asp.net+jquery.form实现图片异步上传的方法(附jquery.form.js下载)
May 05 #Javascript
Bootstrap嵌入jqGrid,使你的table牛逼起来
May 05 #Javascript
Struts2+jquery.form.js实现图片与文件上传的方法
May 05 #Javascript
使用jquery.form.js实现图片上传的方法
May 05 #Javascript
探讨:JavaScript ECAMScript5 新特性之get/set访问器
May 05 #Javascript
浅析JS异步加载进度条
May 05 #Javascript
AngularJS入门(用ng-repeat指令实现循环输出
May 05 #Javascript
You might like
paypal即时到账php实现代码
2010/11/28 PHP
php 高性能书写
2010/12/11 PHP
php7下的filesize函数
2019/09/30 PHP
javascript document.images实例
2008/05/27 Javascript
DWZ table的原生分页浅谈
2013/03/01 Javascript
append和appendTo的区别以及appendChild用法
2013/12/24 Javascript
什么是 AngularJS?AngularJS简介
2014/12/06 Javascript
html的DOM中Event对象onabort事件用法实例
2015/01/21 Javascript
JS绘制生成花瓣效果的方法
2015/08/05 Javascript
jQuery下拉菜单的实现代码
2016/11/03 Javascript
Mac下使用charles遇到的问题以及解决办法
2017/01/10 Javascript
jQuery Validate 校验多个相同name的方法
2017/05/18 jQuery
AngularJS 的$timeout服务示例代码
2017/09/21 Javascript
JS中的BOM应用
2018/02/02 Javascript
详解JavaScript添加给定的标签选项
2018/09/17 Javascript
node.js连接mysql与基本用法示例
2019/01/05 Javascript
详解如何在JS代码中消灭for循环
2019/12/11 Javascript
Vue实现计算器计算效果
2020/08/17 Javascript
分析Python中设计模式之Decorator装饰器模式的要点
2016/03/02 Python
Python中easy_install 和 pip 的安装及使用
2017/06/05 Python
利用python 更新ssh 远程代码 操作远程服务器的实现代码
2018/02/08 Python
python实现本地图片转存并重命名的示例代码
2018/10/27 Python
python 堆和优先队列的使用详解
2019/03/05 Python
将tensorflow模型打包成PB文件及PB文件读取方式
2020/01/23 Python
Jupyter Notebook折叠输出的内容实例
2020/04/22 Python
Python如何获取文件路径/目录
2020/09/22 Python
鞋子女王塔玛拉·梅隆同名奢侈品牌:Tamara Mellon
2017/11/22 全球购物
联想香港官方网站及网店:Lenovo香港
2018/04/13 全球购物
HelloFresh奥地利:立即订购烹饪盒
2019/02/22 全球购物
Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用?
2015/08/04 面试题
建筑毕业生自我鉴定
2013/10/18 职场文书
最新创业融资计划书
2014/01/19 职场文书
考试没考好检讨书(精选篇)
2014/11/16 职场文书
初中家长评语大全
2014/12/26 职场文书
退休劳动合同怎么写?
2019/10/25 职场文书
动作冒险《Hell Is Us》将采用虚幻5 消灭怪物探索王国
2022/04/13 其他游戏