老生常谈原生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 相关文章推荐
Web开发之JavaScript
Mar 29 Javascript
javascript中普通函数的使用介绍
Dec 19 Javascript
js实现动态改变字体大小代码
Jan 02 Javascript
jQuery实现仿Alipay支付宝首页全屏焦点图切换特效
May 04 Javascript
javascript每日必学之运算符
Feb 16 Javascript
轻松搞定js表单验证
Oct 13 Javascript
angularjs中ng-attr的用法详解
Dec 31 Javascript
微信小程序实战之自定义模态弹窗(8)
Apr 18 Javascript
关于预加载InstantClick的问题解决方法
Sep 12 Javascript
vue中改变滚动条样式的方法
Mar 03 Javascript
微信小程序实现弹幕墙(祝福墙)
Nov 18 Javascript
基于JavaScript实现轮播图效果
Jan 02 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
全国FM电台频率大全 - 11 浙江省
2020/03/11 无线电
用PHP和ACCESS写聊天室(一)
2006/10/09 PHP
在WordPress中使用PHP脚本来判断访客来自什么国家
2015/12/10 PHP
利用php-cli和任务计划实现订单同步功能的方法
2017/05/03 PHP
Mootools 1.2教程(2) DOM选择器
2009/09/14 Javascript
jquery 动态创建元素的方式介绍及应用
2013/04/21 Javascript
JS按回车键实现登录的方法
2014/08/25 Javascript
使用百度地图api实现根据地址查询经纬度
2014/12/11 Javascript
JS+CSS实现Div弹出窗口同时背景变暗的方法
2015/03/04 Javascript
Javascript设计模式之观察者模式(推荐)
2016/03/29 Javascript
利用jQuery实现CheckBox全选/全不选/反选的简单代码
2016/05/31 Javascript
实例讲解JavaScript中instanceof运算符的用法
2016/06/08 Javascript
js中获取URL参数的共用方法getRequest()方法实例详解
2018/10/24 Javascript
vue.js+ElementUI实现进度条提示密码强度效果
2020/01/18 Javascript
JavaScript实现好看的跟随彩色气泡效果
2020/02/06 Javascript
简单介绍Python中的floor()方法
2015/05/15 Python
Python文本处理之按行处理大文件的方法
2018/04/09 Python
python程序封装为win32服务的方法
2021/03/07 Python
python opencv读mp4视频的实例
2018/12/07 Python
Python内存管理实例分析
2019/07/10 Python
Python数学形态学实例分析
2019/09/06 Python
Pytorch修改ResNet模型全连接层进行直接训练实例
2019/09/10 Python
Python函数参数类型及排序原理总结
2019/12/19 Python
pytorch实现特殊的Module--Sqeuential三种写法
2020/01/15 Python
适合Python初学者的一些编程技巧
2020/02/12 Python
html5的canvas方法使用指南
2014/12/15 HTML / CSS
BannerBuzz加拿大:在线定制横幅印刷、广告和标志
2020/03/10 全球购物
莫斯科珠宝厂官方网站:Miuz
2020/09/19 全球购物
销售总监工作职责
2013/11/21 职场文书
办公室主任先进事迹
2014/01/18 职场文书
好员工观后感
2015/06/17 职场文书
公司开业主持词
2015/07/02 职场文书
通讯稿范文
2015/07/22 职场文书
初中政治教学反思
2016/02/23 职场文书
技术入股合作协议书
2016/03/21 职场文书
浅谈mysql执行过程以及顺序
2021/05/12 MySQL