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 相关文章推荐
javascript数组操作(创建、元素删除、数组的拷贝)
Apr 07 Javascript
jQuery中的$.ajax()方法应用
May 06 Javascript
ECMAScript6新增值比较函数Object.is
Jun 12 Javascript
js实现无限级树形导航列表效果代码
Sep 23 Javascript
JS 仿支付宝input文本输入框放大组件的实例
Nov 14 Javascript
初探js和简单隐藏效果的实例
Nov 23 Javascript
JS使用正则表达式获取小括号、中括号及花括号内容的方法示例
Jun 01 Javascript
vue实现element-ui对话框可拖拽功能
Aug 17 Javascript
详解Vue中组件传值的多重实现方式
Aug 16 Javascript
浅谈vuex的基本用法和mapaction传值问题
Nov 08 Javascript
vue-cli3.X快速创建项目的方法步骤
Nov 14 Javascript
原生JS实现九宫格抽奖
Sep 13 Javascript
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
php session 预定义数组
2009/03/16 PHP
php flv视频时间获取函数
2010/06/29 PHP
php 深入理解strtotime函数的使用详解
2013/05/23 PHP
基础的WordPress插件制作教程
2015/11/24 PHP
Yii2框架中日志的使用方法分析
2017/05/22 PHP
利用PHP获取汉字首字母并且分组排序详解
2017/10/22 PHP
php字符串函数 str类常见用法示例
2020/05/15 PHP
div+css布局的图片连续滚动js实现代码
2010/05/04 Javascript
js中的this关键字详解
2013/09/25 Javascript
键盘上一张下一张兼容IE/google/firefox等浏览器
2014/01/28 Javascript
node.js中的fs.readdirSync方法使用说明
2014/12/17 Javascript
原生javaScript实现图片延时加载的方法
2014/12/22 Javascript
JS获取表格内指定单元格html内容的方法
2015/03/31 Javascript
3种vue路由传参的基本模式
2018/02/22 Javascript
vue使用ElementUI时导航栏默认展开功能的实现
2018/07/04 Javascript
jQuery--遍历操作实例小结【后代、同胞及过滤】
2020/05/22 jQuery
react的hooks的用法详解
2020/10/12 Javascript
antd配置config-overrides.js文件的操作
2020/10/31 Javascript
解决vue 使用axios.all()方法发起多个请求控制台报错的问题
2020/11/09 Javascript
python文件和目录操作方法大全(含实例)
2014/03/12 Python
在Python中使用zlib模块进行数据压缩的教程
2015/06/26 Python
python制作花瓣网美女图片爬虫
2015/10/28 Python
用Python写王者荣耀刷金币脚本
2017/12/21 Python
Python多重继承的方法解析执行顺序实例分析
2018/05/26 Python
快速解决Django关闭Debug模式无法加载media图片与static静态文件
2020/04/07 Python
CSS3实现swap交换动画
2016/01/19 HTML / CSS
For Art’s Sake官网:手工制作的奢华眼镜
2018/12/15 全球购物
Hotels.com韩国:海外国内旅行所需的酒店和住宿预订网站
2020/05/08 全球购物
.NET程序员的几道面试题
2012/06/01 面试题
宾馆总经理岗位职责
2014/02/14 职场文书
财政专业求职信范文
2014/02/19 职场文书
仓库文员岗位职责
2014/04/06 职场文书
离婚上诉状范文
2015/05/23 职场文书
离婚协议书范本(2016最新版)
2016/03/18 职场文书
中国式结婚:司仪主持词(范文)
2019/07/25 职场文书
使用python绘制横竖条形图
2022/04/21 Python