老生常谈原生JS执行环境与作用域


Posted in Javascript onNovember 22, 2016

首先,我们要知道执行环境和作用域是两个完全不同的概念。

函数的每次调用都有与之紧密相关的作用域和执行环境。从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即window对象)。

换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的。执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

执行环境(也称执行上下文?execution context)

当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境,从此刻开始,函数的每次调用都会创建一个新的执行环境。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中(execution stack)。在函数执行完后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个便利的机制控制着。

执行环境可以分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象 activation object),它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this的值也会被最终确定。在执行阶段,代码被解释执行。

Demo:

<script type="text/javascript">
  function Fn1(){
    function Fn2(){
      alert(document.body.tagName);//BODY
      //other code...
    }
    Fn2();
  }
  Fn1();
  //code here
</script>

老生常谈原生JS执行环境与作用域

小结

当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。当在全局执行环境中调用执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。

此外还要注意一下几点:

单线程

同步执行

唯一的全局执行环境

局部执行环境的个数没有限制

每次某个函数被调用,就会有个新的局部执行环境为其创建,即使是多次调用的自身函数(即一个函数被调用多次,也会创建多个不同的局部执行环境)。

作用域

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。

作用域链包含了执行环境栈中的每个执行环境对应的变量对象。通过作用域链,可以决定变量的访问和标识符的解析。

注意:全局执行环境的变量对象始终都是作用域链的最后一个对象。

在访问变量时,就必须存在一个可见性的问题(内层环境可以访问外层中的变量和函数,而外层环境不能访问内层的变量和函数)。更深入的说,当访问一个变量或调用一个函数时,JavaScript引擎将不同执行环境中的变量对象按照规则构建一个链表,在访问一个变量时,先在链表的第一个变量对象上查找,如果没有找到则继续在第二个变量对象上查找,直到搜索到全局执行环境的变量对象即window对象。这也就形成了Scope Chain的概念。

老生常谈原生JS执行环境与作用域

作用域链图,清楚的表达了执行环境与作用域的关系(一一对应的关系),作用域与作用域之间的关系(链表结构,由上至下的关系)。

Demo:

var color = "blue";
function changeColor(){
 var anotherColor = "red";
 function swapColors(){
  var tempColor = anotherColor;
  anotherColor = color;
  color = tempColor;
  // 这里可以访问color, anotherColor, 和 tempColor
 }
 // 这里可以访问color 和 anotherColor,但是不能访问 tempColor
 swapColors();
}
changeColor();
// 这里只能访问color
console.log("Color is now " + color);

上述代码一共包括三个执行环境:全局执行环境、changeColor()的局部执行环境、swapColors()的局部执行环境。

全局环境有一个变量color和一个函数changecolor();

changecolor()函数的局部环境中具有一个anothercolor属性和一个swapcolors函数,当然,changecolor函数中可以访问自身以及它外围(即全局环境)中的变量;

swapcolor()函数的局部环境中具有一个变量tempcolor。在该函数内部可以访问上面的两个环境(changecolor和window)中的所有变量,因为那两个环境都是它的父执行环境。

上述代码的作用域链如下图所示:

老生常谈原生JS执行环境与作用域

从上图发现。内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。

标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。

执行环境与作用域的区别与联系

执行环境为全局执行环境和局部执行环境,局部执行环境是函数执行过程中创建的。

作用域链是基于执行环境的变量对象的,由所有执行环境的变量对象(对于函数而言是活动对象,因为在函数执行环境中,变量对象是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO(变量对象)的角色。)共同组成。

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。

小练习

<script type="text/javascript">
(function(){
  a= 5;
  console.log(window.a);//undefined
  var a = 1;//这里会发生变量声明提升
  console.log(a);//1
})();
</script>

window.a之所以是undefined,是因为var a = 1;发生了变量声明提升。相当于如下代码:

<script type="text/javascript">
(function(){
  var a;//a是局部变量
  a = 5;//这里局部环境中有a,就不会找全局中的
  console.log(window.a);//undefined
  a = 1;//这里会发生变量声明提升
  console.log(a);//1
})();
</script>

以上就是小编为大家带来的老生常谈原生JS执行环境与作用域全部内容了,希望大家多多支持三水点靠木~

Javascript 相关文章推荐
javascript 传统事件模型构造的事件监听器实现代码
May 31 Javascript
Javascript 类型转换方法
Oct 24 Javascript
多个表单中如何获得这个文件上传的网址实现js代码
Mar 25 Javascript
jQuery自动切换/点击切换选项卡效果的小例子
Aug 12 Javascript
BootStrap 可编辑表Table格
Nov 24 Javascript
Jquery EasyUI Datagrid右键菜单实现方法
Dec 30 Javascript
微信小程序 小程序制作及动画(animation样式)详解
Jan 06 Javascript
easyui-datagrid特殊字符不能显示的处理方法
Apr 12 Javascript
实现微信小程序的wxml文件和wxss文件在webstrom的支持
Jun 12 Javascript
protractor的安装与基本使用教程
Jul 07 Javascript
vue axios请求拦截实例代码
Mar 29 Javascript
JavaScript实现PC端横向轮播图
Feb 07 Javascript
JS中位置与大小的获取方法
Nov 22 #Javascript
javascript中Date对象的使用总结
Nov 21 #Javascript
Ionic2系列之使用DeepLinker实现指定页面URL
Nov 21 #Javascript
使用Angular.js实现简单的购物车功能
Nov 21 #Javascript
jQuery设置Easyui校验规则(推荐)
Nov 21 #Javascript
JS实现图片上传预览功能
Nov 21 #Javascript
值得分享的JavaScript实现图片轮播组件
Nov 21 #Javascript
You might like
PHP编码规范之注释和文件结构说明
2010/07/09 PHP
PHP测试程序运行时间的类
2012/02/05 PHP
php导出csv数据在浏览器中输出提供下载或保存到文件的示例
2014/04/24 PHP
PHP程序中使用adodb连接不同数据库的代码实例
2015/12/19 PHP
php array_values 返回数组的值实例详解
2016/11/17 PHP
JS 自定义函数缺省值的设置方法
2010/05/05 Javascript
JQuery index()方法使用代码
2010/06/02 Javascript
谷歌浏览器调试JavaScript小技巧
2014/12/29 Javascript
情人节单身的我是如何在敲完代码之后收到12束玫瑰的(javascript)
2015/08/21 Javascript
JavaScript快速切换繁体中文和简体中文的方法及网站支持简繁体切换的绝招
2016/03/07 Javascript
原生js和jquery分别实现横向导航菜单效果
2016/05/13 Javascript
js获取元素的外链样式的简单实现方法
2016/06/06 Javascript
纯JS实现简单的日历
2017/06/26 Javascript
JS自定义滚动条效果简单实现代码
2020/10/27 Javascript
基于nodejs实现微信支付功能
2017/12/20 NodeJs
在vue项目中使用sass的配置方法
2018/03/20 Javascript
Vue 实现复制功能,不需要任何结构内容直接复制方式
2019/11/09 Javascript
JavaScript实现简单随机点名器
2019/11/21 Javascript
微信小程序实现页面监听自定义组件的触发事件
2020/11/01 Javascript
[00:49]完美世界DOTA2联赛10月28日开团时刻:随便打
2020/10/29 DOTA
Django中的CBV和FBV示例介绍
2018/02/25 Python
selenium中get_cookies()和add_cookie()的用法详解
2020/01/06 Python
利用python绘制数据曲线图的实现
2020/04/09 Python
解决pycharm安装第三方库失败的问题
2020/05/09 Python
深入研究HTML5实现图片压缩上传功能
2016/03/25 HTML / CSS
英国著名的药妆网站:Escentual
2016/07/29 全球购物
凯特·丝蓓英国官网:Kate Spade英国
2016/11/07 全球购物
爱普生美国官网:Epson美国
2018/11/05 全球购物
杠杆的科学教学反思
2014/01/10 职场文书
开展批评与自我批评发言材料
2014/05/15 职场文书
测控技术自荐信
2014/06/05 职场文书
电话客服工作职责
2014/07/27 职场文书
2014年重阳节老干部座谈会上的讲话稿
2014/09/25 职场文书
群众路线自我剖析材料
2014/10/08 职场文书
群众路线个人整改方案
2014/10/25 职场文书
Go语言设计模式之结构型模式
2021/06/22 Golang