老生常谈原生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 相关文章推荐
使用jquery获取网页中图片高度的两种方法
Sep 26 Javascript
javascript中的nextSibling使用陷(da)阱(keng)
May 05 Javascript
js+css实现select的美化效果
Mar 24 Javascript
简单的js表格操作
Sep 24 Javascript
微信小程序  wx.request合法域名配置详解
Nov 23 Javascript
关于vue-router的那些事儿
May 23 Javascript
vue+axios+promise实际开发用法详解
Oct 15 Javascript
node.js实现上传文件功能
Jul 15 Javascript
layui动态表头的实现代码
Aug 22 Javascript
解决vue项目刷新后,导航菜单高亮显示的位置不对问题
Nov 01 Javascript
微信小程序页面间传递数组对象方法解析
Nov 06 Javascript
Vue 技巧之控制父类的 slot
Feb 24 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页面间参数传递的四种方法详解
2013/06/09 PHP
解析php做推送服务端实现ios消息推送
2013/07/01 PHP
php中hashtable实现示例分享
2014/02/13 PHP
php function用法如何递归及return和echo区别
2014/03/07 PHP
Laravel框架学习笔记(一)环境搭建
2014/10/15 PHP
PHP让数组中有相同值的组成新的数组实例
2017/12/31 PHP
ThinkPHP5分页paginate代码实例解析
2020/11/10 PHP
js调用后台、后台调用前台等方法总结
2014/04/17 Javascript
浅谈Nodejs观察者模式
2015/10/13 NodeJs
详解Bootstrap按钮
2016/01/04 Javascript
详解vue父子模版嵌套案例
2017/03/04 Javascript
Bootstrap栅格系统的使用详解
2017/10/30 Javascript
ES6 javascript的异步操作实例详解
2017/10/30 Javascript
JavaScript剩余操作符Rest Operator详解
2019/07/20 Javascript
JS实现灯泡开关特效
2020/03/30 Javascript
JavaScript设计模式--桥梁模式引入操作实例分析
2020/05/23 Javascript
在Vuex中Mutations修改状态操作
2020/07/24 Javascript
[57:22]完美世界DOTA2联赛PWL S2 FTD vs PXG 第二场 11.27
2020/12/01 DOTA
[01:20]PWL S2开团时刻第三期——团战可以输 蝙蝠必须死
2020/11/26 DOTA
Python面向对象编程基础解析(一)
2017/10/26 Python
PyQt打开保存对话框的方法和使用详解
2019/02/27 Python
Python流行ORM框架sqlalchemy安装与使用教程
2019/06/04 Python
Django处理Ajax发送的Get请求代码详解
2019/07/29 Python
Python3 把一个列表按指定数目分成多个列表的方式
2019/12/25 Python
python GUI库图形界面开发之PyQt5动态(可拖动控件大小)布局控件QSplitter详细使用方法与实例
2020/03/06 Python
python 基于wx实现音乐播放
2020/11/24 Python
伊芙丽官方旗舰店:中国淑女一线品牌
2017/12/01 全球购物
在购买印度民族服饰:Soch
2020/09/15 全球购物
水污染治理专业毕业生推荐信
2013/11/14 职场文书
幼儿园毕业家长感言
2014/02/10 职场文书
私人会所最新创业计划书范文
2014/03/24 职场文书
明信片寄语大全
2014/04/08 职场文书
解除合同协议书
2014/04/17 职场文书
上班迟到检讨书范文300字
2014/11/02 职场文书
MySQL表的增删改查基础教程
2021/04/07 MySQL
Mysql的Table doesn't exist问题及解决
2022/12/24 MySQL