结合代码图文讲解JavaScript中的作用域与作用域链


Posted in Javascript onJuly 05, 2016

先上三段说明作用域的代码

//==========例1==========
 
var scope='global';
function fn(){
  alert(scope);
  var scope='local';
  alert(scope);
}
fn();    //输出结果?
alert(scope);//输出结果?
 
//===========例2==========
 
var scope='global';
function fn(){
  alert(scope);
  scope='local';
  alert(scope);
}
fn();    //输出结果?
alert(scope);//输出结果?
 
//===========例3=========
 
var scope='global';
function fn(scope){
  alert(scope);
  scope='local';
  alert(scope);
}
fn();    //输出结果?
alert(scope);//输出结果?

这三段代码只有小许差异,但结果缺截然不同,例1分别输出[undefined , local , global],例2分别输出[global , local , local],例3结果输出[undefined , local , global],如果不能答对说明你对javascript的作用域特性还未理解透彻。

什么是作用域?

也许有人会问:变量a的作用域是什么?一会儿又问:函数a的作用域是什么?变量和函数的作用域分别是啥玩意?

我们先来看看“作用域”是什么意思,“作用域”拆开来就是“作用”和“域”

  • 作用:访问、操作、调用……
  • 域:区域、范围、空间……

作用域就是变量和函数的可访问范围,或者说变量或函数起作用的区域。

1.javascript函数的作用域:

函数内的区域,就是这个函数的作用域,变量和函数在这个区域都可以访问操作。最外层函数外的区域叫全局作用域,函数内的区域叫局部作用域。

2.javascript变量的作用域:

在源代码中变量所在的区域,就是这个变量的作用域,变量在这个区域内可以被访问操作。在全局作用域上定义的变量叫全局变量,在函数内定义的变量叫局部变量。

简单地理解,JS源代码被函数{ }划分成一块一块的区域,这些区域换个身份就是某函数或某变量的作用域,变量的作用域和函数的作用域在源代码中有可能指的是同一块区域。

作用域链
作用域链(Scope Chain)是javascript内部中一种变量、函数查找机制,它决定了变量和函数的作用范围,即作用域,理解作用域链的作用原理,上一篇文章的三个例子也就能理解了,从而知其然也知其所以然。

作用域链是ECMAScript-262说明文档中的概念,javascript引擎是按ECMAScript-262说明文档去实现的,了解javascript引擎的工作原理有利于我们理解javascript的特性,但绝大多数js程序员不会去了解非常底层的技术,所以阅读ECMAScript-262说明文档,我们可以有一个直观的方式去模拟javascript引擎的工作原理。

本文将通过1999年的ECMAScript-262-3th第三版来说明作用域链的形成原理,将会介绍执行环境,变量对象和活动对象,arguments对象,作用域链等几个概念。2009年发布了ECMAScript-262-5th第五版,不同的是取消了变量对象和活动对象等概念,引入了词法环境(Lexical Environments)、环境记录(EnviromentRecord)等新的概念,所以两个版本的概念不要混淆了。

1.执行环境(Execution Contexts)

执行环境(Execution Contexts)也被翻译为执行上下文,当解析器进入ECMAScript的可执行代码,解析器就进入一个执行环境,活动的执行环境组成一个逻辑上的栈,在这个逻辑栈顶部的执行环境是当前运行的执行环境。

注:ECMAScript中有三种可执行代码,Global、Function和Eval,全局环境即是Global可执行代码,函数即是Function可执行代码。逻辑栈是一种特殊的数据存储格式,特点是‘先进后出,后进先出',添加数据会先压入逻辑栈顶部,删除数据必须先从顶部开始删除。

结合代码图文讲解JavaScript中的作用域与作用域链

变量对象(Variable Object)、活动对象(Activation Object)和Arguments对象(Arguments Object)

每个执行环境都有一个与之关联的变量对象,当解析器进入执行环境时,就会创建一个变量对象,变量对象保存着在当前执行环境中声明的变量和函数的引用。

变量对象是一个抽象的概念,在不同的执行环境中,变量对象有不同的身份,在解析器进入任何执行环境之前,就已经创建了一个Global对象,当解析器进入全局执行环境时,Global对象就充当变量对象,当解析器进入一个函数时,就会创建一个活动对象充当变量对象。

2.解析器处理代码时的两个阶段

我们都知道javascript解析器是一段一段解析处理代码的,为毛?这就要涉及解析器处理代码时的两个阶段,解析代码和执行代码。

当解析器进入执行环境时,变量对象就会添加执行环境中声明的变量和函数作为它的属性,这就意味着变量和函数在声明之前已经可用,变量值为undefined,这就是变量和函数声明提升(Hoisting)的原因,与此同时作用域链和this确定,此过程为解析阶段,俗称预解析。接着解析器开始执行代码,为变量添加相应值的引用,得到执行结果,此过程为执行阶段。

举两个好吃的栗子:

var a=123;
var b="abc";
function c(){
  alert('11');
}

上述全局环境中的代码解析执行后,会将Global对象作为变量对象,保存以下数据。

结合代码图文讲解JavaScript中的作用域与作用域链

function testFn(a){
  var b="123";
  function c(){
    alert("abc");
  }
}
 
testFn(10);

当解析器进入函数执行环境时,则会创建一个活动对象作为变量对象,活动对象还会创建一个Arguments对象,arguments对象是一个参数集合,用来保存参数,这就是我们写函数时可以使用arguments[0]等来使用参数的原因。

结合代码图文讲解JavaScript中的作用域与作用域链

3.作用域链(Scope Chain)

每个执行环境都有一个与之关联的作用域链,当解析器进入执行环境时被定义,作用域链是一个对象列表,用来检索各个变量对象中的变量和函数,这样可以保证执行环境有权访问哪些变量和函数,举个栗子。

var a='123';
function testFn(b){
  var c='abc';
 
  function testFn2(){
    var d='efg';
    alert(a);
  }
 
  testFn2();
}
 
testFn(10);

testFn2内未声明变量a,为什么testFn2能调用全局变量a?整个过程是怎么发生的呢?请看下图。

结合代码图文讲解JavaScript中的作用域与作用域链

当解析器进入全局执行环境时,调用变量和函数时只在Global对象中查找。

当解析器进入testFn函数执行环境时,函数内部属性[[scope]]中首先填入Global对象,然后将testFn活动对象添加到Global对象之前,形成一个作用域链。

结合代码图文讲解JavaScript中的作用域与作用域链

当解析器进入testFn2函数执行环境时,函数内部属性[[scope]]首先填入父级的作用域链,然后再将当前的testFn2活动对象添加到作用域链的前端,形成一个新的作用域链。

testFn2调用变量a时,首先在当前的testFn2活动对象中查找,如果没有找到就顺着作用域链向上,在testFn活动对象中查找变量a,如果没有找到再顺着作用域链向上查找,直到在最后Global对象中找到为止,否则报错。所以函数内部可以调用外部环境的变量,外部环境不能调用函数内部的变量,这就是作用域特性的原理。

Javascript 相关文章推荐
JavaScrip单线程引擎工作原理分析
Sep 04 Javascript
读jQuery之五(取DOM元素)
Jun 20 Javascript
JavaScript单元测试ABC
Apr 12 Javascript
基于jquery ajax 用户无刷新登录方法详解
Apr 28 Javascript
JQuery切换显示的效果实例代码
Feb 27 Javascript
使用JavaScript获取电池状态的方法
May 03 Javascript
JQEasy-ui在IE9以下版本中二次加载的问题分析及处理方法
Jun 23 Javascript
jQuery及JS实现循环中暂停的方法
Feb 02 Javascript
浅谈Javascript中的12种DOM节点类型
Aug 19 Javascript
打造自己的jQuery插件入门教程
Sep 23 Javascript
微信小程序使用form表单获取输入框数据的实例代码
May 17 Javascript
Node登录权限验证token验证实现的方法示例
May 25 Javascript
jQuery的Each比JS原生for循环性能慢很多的原因
Jul 05 #Javascript
Node.js实现文件上传
Jul 05 #Javascript
JavaScript数组方法大全(推荐)
Jul 05 #Javascript
JS与HTML结合使用marquee标签实现无缝滚动效果代码
Jul 05 #Javascript
js利用正则表达式检验输入内容是否为网址
Jul 05 #Javascript
后端接收不到AngularJs中$http.post发送的数据原因分析及解决办法
Jul 05 #Javascript
微信JS-SDK坐标位置如何转换为百度地图坐标
Jul 04 #Javascript
You might like
php 代码优化之经典示例
2011/03/24 PHP
ThinkPHP自动填充实现无限级分类的方法
2014/08/22 PHP
PHP图片处理之使用imagecopyresampled函数实现图片缩放例子
2014/11/19 PHP
在Mac OS上搭建Nginx+PHP+MySQL开发环境的教程
2015/12/21 PHP
PHP5.5迭代生成器用法实例详解
2016/03/16 PHP
PHP的邮件群发系统phplist配置方法详细总结
2016/03/30 PHP
老生常谈PHP面向对象之注册表模式
2017/05/26 PHP
浅谈php使用curl模拟多线程发送请求
2019/03/08 PHP
解决AJAX中跨域访问出现'没有权限'的错误
2008/08/20 Javascript
JavaScript SetInterval与setTimeout使用方法详解
2013/11/15 Javascript
document.compatMode的CSS1compat使用介绍
2014/04/03 Javascript
node.js中的dns.getServers方法使用说明
2014/12/08 Javascript
node.js中的http.response.write方法使用说明
2014/12/14 Javascript
JavaScript 常见安全漏洞和自动化检测技术
2015/08/21 Javascript
js实现左侧网页tab滑动门效果代码
2015/09/06 Javascript
jQuery简单实现仿京东分类导航层效果
2016/06/07 Javascript
自动化测试读写64位操作系统的注册表
2016/08/15 Javascript
js当前页面登录注册框,固定div,底层阴影的实例代码
2016/10/04 Javascript
jQuery实现移动端Tab选项卡效果
2017/03/15 Javascript
jQuery插件FusionCharts绘制的2D帕累托图效果示例【附demo源码】
2017/03/28 jQuery
基于JS实现仿京东搜索栏随滑动透明度渐变效果
2017/07/10 Javascript
用react-redux实现react组件之间数据共享的方法
2018/06/08 Javascript
深入分析element ScrollBar滚动组件源码
2019/01/22 Javascript
js实现一个简易计算器
2020/03/30 Javascript
小程序input数据双向绑定实现方法
2019/10/17 Javascript
python网络编程之UDP通信实例(含服务器端、客户端、UDP广播例子)
2014/04/25 Python
python 使用建议与技巧分享(四)
2020/08/18 Python
python 两种方法修改文件的创建时间、修改时间、访问时间
2020/09/26 Python
国际贸易专业个人求职信格式
2014/02/02 职场文书
护理人员的自我评价分享
2014/03/15 职场文书
2014入党积极分子破除“四风”思想汇报
2014/09/14 职场文书
国际商务专业毕业生自我鉴定2014
2014/09/27 职场文书
教师党员个人自我剖析材料
2014/09/29 职场文书
2014年人事部工作总结
2014/12/03 职场文书
医药公司采购员岗位职责
2015/04/03 职场文书
Spring Boot 的创建和运行示例代码详解
2022/07/23 Java/Android