js 执行上下文和作用域的相关总结


Posted in Javascript onFebruary 08, 2021

前言

如果你是或者你想成为一名合格的前端开发工作者,你必须知道JavaScript代码在执行过程,知道执行上下文、作用域、变量提升等相关概念,并且熟练应用到自己的代码中。本文参考了你不知道的JavaScript,和JavaScript高级程序设计,以及部分博客。

正文

     1.JavaScript代码的执行过程相关概念

js代码的执行分为编译器的编译和js引擎与作用域执行两个阶段,其中编译器编译的阶段(预编译阶段)分为分词/词法分析、解析/语法分析、代码生成三个阶段。

   (1)在分词/词法分析阶段,编译器负责将代码进行分割处理,将语句分割成词法单元流/数组;

   (2)在解析/词法分析阶段,将上一阶段的词法单元流转换成由元素嵌套组成的符合程序语法结构的抽象语法树;

   (3)在代码生成阶段,将抽象语法树转换成可执行代码,并交付给js引擎。

js代码执行的三个重要角色:

(1)js引擎:负责代码执行的整个过程

(2)编译器:负责js代码语法解析和生成可执行代码

(3)作用域:手机并维护所有声明标识符,根据特定规则确定当前代码对声明的标识符的访问权限

 2. 执行上下文和执行栈

每当js代码在运行的时候,它都是在执行上下文中运行。说到执行上下文,需要知道什么时执行栈,执行栈,就是其他编程语言中的“调用栈”,是一种拥有LIFO(后进先出)数据结构的栈,被用来存储代码运行时所创建的执行上下文。当js引擎第一次遇到要执行的代码的时候,首先会创建一个全局的执行上下文并压入当前执行栈,每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈顶,js引擎执行栈顶的函数,当该函数执行完毕,执行上下文从栈中弹出,控制流程到达下一个上下文。对于每一个执行上下文都含有三个重要属性:变量对象,作用域链,this。这些属性也需要彻底理解。

2.1 、上下文调用栈

var scope1 = "global scope";
 function checkscope1(){
 var scope1 = "local scope";
 function f(){
 console.log(scope1); 
 }
 return f();
 }
 checkscope1();
var scope2 = "global scope";
 function checkscope2(){
 var scope2 = "local scope";
 function f(){
 console.log(scope2);
 }
 return f;
 }
 checkscope2()();

上面两段代码都会输出 local scope

    上面代码中scope一定是局部变量,查找块级作用域即可,不管何时何地执行 f(),这种绑定在执行f()时依然有效。出现了一样的结果,但是两段代码的执行上下文栈的变化不一样 :

第一段代码:push(<checkscope1>functionContext)=>push(<f>functionContext)=>pop()=>pop()

    第二段代码:push(<checkscope2>functionContext)=>pop()=>push(<f>functionContext)=>pop()

2.2 、三种执行上下文类型

(1)全局上下文

js引擎开始解析js代码的时候首先遇到的就是全局代码,初始化的时候会在调用栈中压入一个全局执行的上下文,当整个应用程序结束的时候才会清空执行上下文栈,栈的最底部永远时全局执行上下文。这是默认的或者说基础的全局作用域,任何函数内部的代码都在全局作用域中,首先创建一个全局的window对象,然后设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。在顶层js代码中可以使用this引用全局对象,因为全局对象时是域链的头,意味着所有非限定性的变量和函数都作为该对象的函数来查询。

总之,全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的window对象,我们能通过this直接访问到它。

(2)函数上下文

每当一个函数被调用是,都会外该函数创建一个新的上下文,每个函数都拥有自己的上下文,不过是在函数调用的时候创建的,需要注意的是同一个函数被多次调用,都会创建一个新的上下文。

(3)eval和with上下文

执行在 eval和with 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

2.3 、执行上下文创建阶段

执行上下文创建分为创建阶段与执行阶段两个阶段

js引擎在执行上下文创建阶段主要负责三件事:确定this==>创建词法环境组件==>创建变量环境组件(目前还不太理解)

(1)确定this,这个不做详解

(2)创建词法环境组件

词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。其中环境记录用于存储当前环境中的变量和函数声明的实际位置;外部环境引入记录很好理解,它用于保存自身环境可以访问的其它外部环境,那么说到这个,是不是有点作用域链的意思?

词法环境有两种类型:

  • 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是 null。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
  • 在函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

(3)创建变量环境组件

变量环境可以说也是词法环境,它具备词法环境所有属性,一样有环境记录与外部环境引入。在ES6中唯一的区别在于词法环境用于存储函数声明与let const声明的变量,而变量环境仅仅存储var声明的变量。

3. JavaScript作用域和作用域链

3.1、作用域

词法作用域是在写代码或者定义的时候确定的,而动态作用域是在运行时确定的,(this也是)词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用,JavaScript采用词法作用域,其作用域由你在写代码是将变量和块作用域写在哪里决定,因此当词法分析器处理代码时会保持作用域不变。可以理解为作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

理解作用域之前先来看一道题

function foo() {
 console.log(value);
 }
 var value = 1;
 function bar() {
 var value = 2;
 console.log(value);
 foo();
 }
 bar();

上面的代码会输出什么呢,首先在全局上下文中声明foo()函数、value变量(其值为undefined)、bar()函数,代码执行阶段,bar函数上下文入栈并执行,打印出value为2,然后执行foo(),foo()入栈,打印value时找不到该变量,js引擎会查找上层作用域,即全局作用域,于是打印出1。后面函数执行完毕上下文出栈。再来看下面这个函数,作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。

js 执行上下文和作用域的相关总结

ES6以来,js中的作用域分为全局作用域,函数作用域,块级作用域和欺骗作用域。

3.1.1、全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域,最外层函数和在最外层函数外面定义的变量拥有全局作用域,所有末定义直接赋值的变量自动声明为拥有全局作用域。

3.1.2、函数作用域

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用);
      这个原则是指在软件设计中,应该最小限度地暴露必 要内容,而将其他内容都“隐藏”起来;
      函数表达式可以是匿名的, 而函数声明则不可以省略函数名。

3.1.3、块作用域

块作用域,通常指 { .. } 内部
        (1)if 、 try/catch创建块作用域;
        (2)let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部);
        (3)for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值;
        (4)const同样可以用来创建块作用域变量,但其值是固定的 (常量)。创建对象时值可以被改变。

3.1.4、欺骗词法作用域的方法,eval()和with()

  eval()参数为一个字符串,并把里面的内容当作书写在该位置的代码一样处理(非严格模式);

 with()当需要重复引用一个对象的多个属性时,可以不需要重复引用对象本身。

3.2、作用域链

  作用域链本质上就是根据名称查找变量(标识符名称)的一套规则。规则非常简单,在自己的变量对象里找不到变量,就上父级的变量对象查找,当抵达最外层的全局上下文中,无论找到还是没找到,查找过程都会停止。查找会在找到第一个匹配的变量时停止,被称为遮蔽效应

 作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问                              
        作用域链:当函数定义时,系统生成([scope])属性,该属性保存该函数的作用域链,该作用域链的第0位存储当前环境下的全局执行期上下文GO,GO里存储全局下的所有对象,其中包含函数和全局变量,当函数执行的前一刻,预编译的时候,作用域链的顶端(第0位)存储函数生成的执行上下文AO,同时第一位存储GO
        查找变量是到函数存储的作用域链中从顶端开始依次向下查找(函数内部作用域在最顶端,证明了函数可以访问外部的变量,而外部无法访问函数内部的变量)

4.执行上下文和作用域的区别

每个函数调用都有与之相关的作用域和上下文。从根本上说,范围是基于函数(function-based)而上下文是基于对象(object-based)。换句话说, 作用域是和每次函数调用时变量的访问有关,并且每次调用都是独立的。上下文总是关键字 this 的值,是调用当前可执行代码的对象的引用。作用域是函数定义的时候就确定好的了,函数当中的变量是和函数所处的作用域有关,函数运行的作用域也是与该函数定义时的作用域有关。而上下文,主要是关键字this的值,这个是由函数运行时决定的,简单来说就是谁调用此函数,this就指向谁。

5.最后

以上就是js 执行上下文和作用域的相关总结的详细内容,更多关于js 执行上下文和作用域的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
js特效,页面下雪的小例子
Jun 17 Javascript
js history对象简单实现返回和前进
Oct 30 Javascript
使用POST方式弹出窗口的两种方法示例介绍
Jan 29 Javascript
JavaScript onkeydown事件入门实例(键盘某个按键被按下)
Oct 17 Javascript
AngularJS入门教程之AngularJS模型
Apr 18 Javascript
5个最顶级jQuery图表类库插件【jquery插件库】
May 05 Javascript
js中获取时间new Date()的全面介绍
Jun 20 Javascript
AngularJS使用带属性值的ng-app指令实现自定义模块自动加载的方法
Jan 04 Javascript
Vue2 使用 Echarts 创建图表实例代码
May 18 Javascript
JavaScript使用类似break机制中断forEach循环的方法
Nov 13 Javascript
原生js实现trigger方法示例代码
May 22 Javascript
eslint+prettier统一代码风格的实现方法
Jul 22 Javascript
微信小程序tab左右滑动切换功能的实现代码
Feb 08 #Javascript
用Javascript实现发送短信验证码间隔功能
Feb 08 #Javascript
原生js 实现表单验证功能
Feb 08 #Javascript
js面向对象封装级联下拉菜单列表的实现步骤
Feb 08 #Javascript
JavaScript实现点击出现子菜单效果
Feb 08 #Javascript
深入理解javascript中的this
Feb 08 #Javascript
Vue中使用wangeditor富文本编辑的问题
Feb 07 #Vue.js
You might like
改变Apache端口等配置修改方法
2008/06/05 PHP
Pain 全世界最小最简单的PHP模板引擎 (普通版)
2011/10/23 PHP
php和js如何通过json互相传递数据相关问题探讨
2013/02/26 PHP
PHP实现数组的笛卡尔积运算示例
2017/12/15 PHP
HTTP状态代码以及定义(解释)
2007/02/02 Javascript
不同浏览器的怪癖小结
2010/07/11 Javascript
jquery-easyui关闭tab自动切换到前一个tab
2010/07/29 Javascript
ASP中Sub和Function的区别说明
2020/08/30 Javascript
常见效果实现之返回顶部(结合淡入、淡出、减速滚动)
2012/01/04 Javascript
jQuery EasyUI Dialog拖不下来如何解决
2015/09/28 Javascript
jQuery新窗口打开外链接
2016/07/21 Javascript
jQuery解析返回的xml和json方法详解
2017/01/05 Javascript
JQuery异步提交表单与文件上传功能示例
2017/01/12 Javascript
js学习总结之dom2级事件基础知识详解
2017/07/27 Javascript
Node.js折腾记一:读指定文件夹,输出该文件夹的文件树详解
2019/04/20 Javascript
vue动态绑定class的几种常用方式小结
2019/05/21 Javascript
详解js中的原型,原型对象,原型链
2020/07/16 Javascript
[38:30]2014 DOTA2国际邀请赛中国区预选赛 LGD-GAMING VS CIS 第一场2
2014/05/24 DOTA
[33:33]完美世界DOTA2联赛PWL S2 FTD.C vs SZ 第二场 11.27
2020/11/30 DOTA
python使用BeautifulSoup分页网页中超链接的方法
2015/04/04 Python
python实现从字典中删除元素的方法
2015/05/04 Python
Python在图片中添加文字的两种方法
2017/04/29 Python
Python获取系统所有进程PID及进程名称的方法示例
2018/05/24 Python
python协程之动态添加任务的方法
2019/02/19 Python
Python 基于FIR实现Hilbert滤波器求信号包络详解
2020/02/26 Python
使用Python对Dicom文件进行读取与写入的实现
2020/04/20 Python
python seaborn heatmap可视化相关性矩阵实例
2020/06/03 Python
浅谈Python中的生成器和迭代器
2020/06/19 Python
Python 串口通信的实现
2020/09/29 Python
通过一张图教会你CSS3倒影的实现
2017/09/26 HTML / CSS
声明struct x1 { . . . }; 和typedef struct { . . . }x2;有什么不同
2012/06/02 面试题
2015年采购员工作总结
2015/04/27 职场文书
2015年超市员工工作总结
2015/05/04 职场文书
电台广播稿范文
2015/08/19 职场文书
OpenCV-Python实现油画效果的实例
2021/06/08 Python
Node.js实现爬取网站图片的示例代码
2022/04/04 NodeJs