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实现给指定元素的后面追加内容
Apr 10 Javascript
jquery定时滑出可最小化的底部提示层特效代码
Oct 02 Javascript
jquery ajax应用中iframe自适应高度问题解决方法
Apr 12 Javascript
node.js中的fs.openSync方法使用说明
Dec 17 Javascript
AngularJs Managing Service Dependencies详解
Sep 02 Javascript
vue监听滚动事件实现滚动监听
Apr 11 Javascript
关于js中的鼠标事件总结
Jul 11 Javascript
JavaScript实现简单生成随机颜色的方法
Sep 21 Javascript
tween.js缓动补间动画算法示例
Feb 13 Javascript
基于vue-cli 打包时抽离项目相关配置文件详解
Mar 07 Javascript
解决layer.msg 不居中 ifram中的问题
Sep 05 Javascript
微信小程序scroll-view实现滚动到锚点左侧导航栏点餐功能(点击种类,滚动到锚点)
Jun 11 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
php在项目中寻找代码的坏味道(综艺命名)
2012/07/19 PHP
php操作redis缓存方法分享
2015/06/03 PHP
php使用ftp远程上传文件类(完美解决主从文件同步问题的方法)
2016/09/23 PHP
Laravel 集成微信用户登录和绑定的实现
2019/12/27 PHP
jquery中实现简单的tabs插件功能的代码
2011/03/02 Javascript
Javascript 加载和执行-性能提高篇
2012/12/28 Javascript
通过上下左右键和回车键切换光标实现代码
2013/03/08 Javascript
javascript实现了照片拖拽点击置顶的照片墙代码
2015/04/03 Javascript
jQuery使用cookie与json简单实现购物车功能
2016/04/15 Javascript
jQuery侧边栏实现代码
2016/05/06 Javascript
30分钟快速掌握Bootstrap框架
2016/05/24 Javascript
基于bootstrap实现广告轮播带图片和文字效果
2016/07/22 Javascript
JavaScript数组复制详解
2017/02/02 Javascript
微信小程序教程系列之视图层的条件渲染(10)
2017/04/19 Javascript
JavaScript基于replace+正则实现ES6的字符串模版功能
2017/04/25 Javascript
vue基于mint-ui的城市选择3级联动的示例
2017/10/25 Javascript
jQuery 防止相同的事件快速重复触发方法
2018/02/08 jQuery
vue mounted组件的使用
2018/06/18 Javascript
layui radio性别单选框赋值方法
2018/08/15 Javascript
vue中的自定义分页插件组件的示例
2018/08/18 Javascript
详解用JS添加和删除class类名
2019/03/25 Javascript
vue中使用百度脑图kityminder-core二次开发的实现
2019/09/26 Javascript
js的Object.assign用法示例分析
2020/03/05 Javascript
详解vue 中 scoped 样式作用域的规则
2020/09/14 Javascript
pymongo实现多结果进行多列排序的方法
2015/05/16 Python
Python基于pandas爬取网页表格数据
2020/05/11 Python
Python Dataframe常见索引方式详解
2020/05/27 Python
使用Python获取爱奇艺电视剧弹幕数据的示例代码
2021/01/12 Python
移动端html5模拟长按事件的实现方法
2018/09/30 HTML / CSS
卡骆驰德国官方网站:Crocs德国
2019/03/29 全球购物
追悼会子女答谢词
2014/01/28 职场文书
化学专业毕业生求职信
2014/07/28 职场文书
小学教师个人工作总结2015
2015/04/20 职场文书
2019年教师入党申请书
2019/06/27 职场文书
中国式结婚:司仪主持词(范文)
2019/07/25 职场文书
python3实现Dijkstra算法最短路径的实现
2021/05/12 Python