老生常谈原生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实现购物车多物品数量的加减+总价计算
Jun 06 Javascript
详解AngularJS中$http缓存以及处理多个$http请求的方法
Feb 06 Javascript
修改Jquery Dialog 位置的实现方法
Aug 26 Javascript
滚动条的监听与内容随着滚动条动态加载的实现
Feb 08 Javascript
Vue-Router进阶之滚动行为详解
Sep 13 Javascript
AngularJS对动态增加的DOM实现ng-keyup事件示例
Mar 12 Javascript
jQuery实现的淡入淡出图片轮播效果示例
Aug 29 jQuery
React和Vue中监听变量变化的方法
Nov 14 Javascript
JS使用Chrome浏览器实现调试线上代码
Jul 23 Javascript
es5 类与es6中class的区别小结
Nov 09 Javascript
vue组件中传值EventBus的使用及注意事项说明
Nov 16 Javascript
vue脚手架项目创建步骤详解
Mar 02 Vue.js
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
Cakephp 执行主要流程
2010/03/24 PHP
PHP与SQL注入攻击防范小技巧
2011/09/16 PHP
yii2项目实战之restful api授权验证详解
2017/05/20 PHP
php微信开发之关键词回复功能
2018/06/13 PHP
体验js中splice()的强大(插入、删除或替换数组的元素)
2013/01/16 Javascript
JS/jQuery实现默认显示部分文字点击按钮显示全部内容
2013/05/13 Javascript
JavaScript获取多个数组的交集简单实例
2013/11/11 Javascript
javaScript中push函数用法实例分析
2015/06/08 Javascript
用v-html解决Vue.js渲染中html标签不被解析的问题
2016/12/14 Javascript
微信小程序 picker 组件详解及简单实例
2017/01/10 Javascript
axios基本入门用法教程
2017/03/25 Javascript
JS中mouseup事件丢失的原因与解决办法
2017/06/14 Javascript
仿京东快报向上滚动的实例
2017/12/13 Javascript
在ES5与ES6环境下处理函数默认参数的实现方法
2018/05/13 Javascript
GOJS+VUE实现流程图效果
2018/12/01 Javascript
微信小程序实现左侧滑动导航栏
2020/04/08 Javascript
原生JavaScript实现的无缝滚动功能详解
2020/01/17 Javascript
[01:31:22]DOTA2-DPC中国联赛定级赛 LBZS vs Magma BO3第二场 1月10日
2021/03/11 DOTA
python时间整形转标准格式的示例分享
2014/02/14 Python
pandas中遍历dataframe的每一个元素的实现
2019/10/23 Python
Cython编译python为so 代码加密示例
2019/12/23 Python
基于HTML5 audio元素播放声音jQuery小插件
2011/05/11 HTML / CSS
基于HTML5陀螺仪实现ofo首页眼睛移动效果的示例
2017/07/31 HTML / CSS
迪卡侬波兰体育用品商店:Decathlon波兰
2020/03/31 全球购物
就业自荐书
2013/12/05 职场文书
电子技术专业中专生的自我评价
2013/12/17 职场文书
办公室助理岗位职责
2013/12/25 职场文书
三八节主持词
2014/03/17 职场文书
故意伤害人身损害赔偿协议书
2014/11/19 职场文书
撤诉申请书法院范本
2015/05/18 职场文书
2015年幼师个人工作总结
2015/10/15 职场文书
编写python程序的90条建议
2021/04/14 Python
详解vue中v-for的key唯一性
2021/05/15 Vue.js
python 如何用terminal输入参数
2021/05/25 Python
一文搞懂redux在react中的初步用法
2021/06/09 Javascript
Spring this调用当前类方法无法拦截的示例代码
2022/03/20 Java/Android