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 相关文章推荐
几个高效,简洁的字符处理函数
Apr 12 Javascript
测试你的JS的掌握程度的代码
Dec 09 Javascript
js中AppendChild与insertBefore的用法详细解析
Dec 16 Javascript
JavaScript调试技巧之console.log()详解
Mar 19 Javascript
jQuery实现鼠标滚轮动态改变样式或效果
Jan 05 Javascript
浅谈nodeName,nodeValue,nodeType,typeof 的区别
Jan 13 Javascript
ES5学习教程之Array对象
Apr 01 Javascript
微信小程序中form 表单提交和取值实例详解
Apr 20 Javascript
微信小程序表单验证错误提示效果
May 19 Javascript
javascript+html5+css3自定义弹出窗口效果
Oct 26 Javascript
在Vue项目中使用snapshot测试的具体使用
Apr 16 Javascript
ES6使用新特性Proxy实现的数据绑定功能实例
May 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 strtok()函数的优点分析
2010/03/02 PHP
php自动提交表单的方法(基于fsockopen与curl)
2016/05/09 PHP
PHP7.1方括号数组符号多值复制及指定键值赋值用法分析
2016/09/26 PHP
php及codeigniter使用session-cookie的方法(详解)
2017/04/06 PHP
PHP调用微博接口实现微博登录的方法示例
2018/09/22 PHP
laravel 实现根据字段不同值做不同查询
2019/10/23 PHP
JQuery 构建客户/服务分离的链接模型中Table中的排序分析
2010/01/22 Javascript
JQuery小知识
2010/10/15 Javascript
基于jQuery选择器的整理集合
2013/04/26 Javascript
js验证IP及子网掩码的合法性有效性示例
2014/04/30 Javascript
JS面向对象基础讲解(工厂模式、构造函数模式、原型模式、混合模式、动态原型模式)
2014/08/16 Javascript
jquery中toggle函数交替使用问题
2015/06/22 Javascript
js仿搜狐视频记录片列表展示效果
2020/05/30 Javascript
详解用webpack2.0构建vue2.0超详细精简版
2017/04/05 Javascript
ztree简介_动力节点Java学院整理
2017/07/19 Javascript
用Vue-cli搭建的项目中引入css报错的原因分析
2017/07/20 Javascript
原生JS实现日历组件的示例代码
2017/09/22 Javascript
VUE前端cookie简单操作
2017/10/17 Javascript
js实现登录与注册界面
2017/11/01 Javascript
JS数组降维的实现Array.prototype.concat.apply([], arr)
2020/04/28 Javascript
vue实现的多页面项目如何优化打包的步骤详解
2020/07/19 Javascript
vue页面引入three.js实现3d动画场景操作
2020/08/10 Javascript
[03:05]DOTA2英雄基础教程 嗜血狂魔
2013/12/10 DOTA
利用Python如何将数据写到CSV文件中
2018/06/05 Python
pandas 实现字典转换成DataFrame的方法
2018/07/04 Python
Python实现微信自动好友验证,自动回复,发送群聊链接方法
2019/02/21 Python
Python定义函数功能与用法实例详解
2019/04/08 Python
python 矢量数据转栅格数据代码实例
2019/09/30 Python
python GUI库图形界面开发之PyQt5控件QTableWidget详细使用方法与属性
2020/02/25 Python
PyTorch加载自己的数据集实例详解
2020/03/18 Python
小学三年级数学教学反思
2014/01/31 职场文书
运动会邀请函范文
2014/02/06 职场文书
《罗布泊,消逝的仙湖》教学反思
2014/03/01 职场文书
教师节获奖感言
2015/07/31 职场文书
2016国培学习心得体会
2016/01/08 职场文书
PHP基本语法
2021/03/31 PHP