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

前言

  如果你是或者你想成为一名合格的前端开发工作者,你必须知道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。后面函数执行完毕上下文出栈。再来看下面这个函数,作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。

  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 执行上下文和作用域的资料请关注我们其它相关文章!

(0)

相关推荐

  • javascript执行上下文、变量对象实例分析

    本文实例讲述了javascript执行上下文.变量对象.分享给大家供大家参考,具体如下: 突然看到一篇远在2010年的老文,作者以章节的形式向我们介绍了ECMA-262-3的部分内容,主要涉及到执行上下文.变量对象.作用域.this等语言细节.内容短小而精悍,文风直白而严谨,读完有酣畅淋漓.醍醐灌顶之感,强烈推荐!!! 原文链接:这里 本想翻译成文,原来早已有人做了,这里.真生不逢时,何其遗憾啊! 做个笔记,聊慰我心. 执行上下文 ExecutionContext 每当控制器(control)转

  • 一篇文章弄懂javascript中的执行栈与执行上下文

    前言 作为一个前端开发人员,弄清楚JavaScript的执行上下文有助于我们理解js中一些晦涩的概念,比如闭包,作用域,变量提升等等. 执行栈 执行栈用于存储代码执行期间创建的所有执行上下文.具有FILO接口,也被称为调用栈. 当JavaScript代码被运行的时候,会创建一个全局上下文,并push到当前执行栈.之后当发生函数调用的时候,引擎会为函数创建一个函数执行上下文并push到栈顶.引擎会先执行调用栈顶部的函数,当函数执行完成后,当前函数的执行上下文会被移除当前执行栈.并移动到下一个上下文

  • 深入理解JavaScript 中的执行上下文和执行栈

    如果你是或者想成为一名 JavaScript 开发者,你必须知道 JavaScript 程序内部是如何执行的.理解执行上下文和执行栈对于理解其他 JavaScript 概念(如变量声明提升,作用域和闭包)至关重要. 正确理解执行上下文和执行栈的概念将使您成为更出色的 JavaScript 开发者. 闲话少说,让我们开始吧 :) 分享自 Bit的博客 使用 Bit 应用所提供的组件作为构建模块,你就是架构师.随时随地和你的团队分享.发现和开发组件,快来尝试鲜! Bit - 分享和创造代码组件: B

  • 通过实例了解JS执行上下文运行原理

    壹 ❀ 引 我们都知道,JS代码的执行顺序总是与代码先后顺序有所差异,当先抛开异步问题你会发现就算是同步代码,它的执行也与你的预期不一致,比如: function f1() { console.log('听风是风'); }; f1(); //echo function f1() { console.log('echo'); }; f1(); //echo 按照代码书写顺序,应该先输出 听风是风,再输出 echo才对,很遗憾,两次输出均为 echo:如果我们将上述代码中的函数声明改为函数表达式,结

  • JavaScript 中的执行上下文和执行栈实例讲解

    JavaScript - 原理系列 ​ 在日常开发中,每当我们接手一个现有项目后,我们总喜欢先去看看别人写的代码.每当我们看到别人写出很酷的代码的时候,我们总会感慨!写出这么优美而又简洁的代码的兄弟到底是怎么养成的呢? ​ 我要怎样才能达到和大佬一样的水平呢!好了,废话不多说,让我们切入今天的主题. 一.执行上下文 ​ 简而言之,[执行上下文]就是JavaScript 代码被解析和执行时所在环境的抽象概念, 在JavaScript 中运行任何的代码都是在它的执行上下文中运行. ​ 在运行Java

  • Javascript执行上下文顺序的深入讲解

    一 执行上下文? 1什么是执行上下文? 执行上下文就是当前的 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行的. 2 执行上下文的类型 执行上下文分为三种类型: 全局执行上下文:只有一个,这是默认的,也是基础的执行上下文.(不在任何函数中的代码都是全局执行上下文)他有两个作用,一个是创建了全局变量,也就是指向window下的变量,另一个是将this的指向全局. 函数执行上下文:有无数个,每个函数都拥有自己的执行上下文,但

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

    前言 如果你是或者你想成为一名合格的前端开发工作者,你必须知道JavaScript代码在执行过程,知道执行上下文.作用域.变量提升等相关概念,并且熟练应用到自己的代码中.本文参考了你不知道的JavaScript,和JavaScript高级程序设计,以及部分博客. 正文     1.JavaScript代码的执行过程相关概念 js代码的执行分为编译器的编译和js引擎与作用域执行两个阶段,其中编译器编译的阶段(预编译阶段)分为分词/词法分析.解析/语法分析.代码生成三个阶段.   (1)在分词/词法

  • 老生常谈原生JS执行环境与作用域

    首先,我们要知道执行环境和作用域是两个完全不同的概念. 函数的每次调用都有与之紧密相关的作用域和执行环境.从根本上来说,作用域是基于函数的,而执行环境是基于对象的(例如:全局执行环境即window对象). 换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的.执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用.每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在

  • 图文详解Javascript中的上下文和作用域

    执行上下文(Execution context) 执行上下文(简称上下文)决定了Js执行过程中可以获取哪些变量.函数.数据,一段程序可能被分割成许多不同的上下文,每一个上下文都会绑定一个变量对象(variable object),它就像一个容器,用来存储当前上下文中所有已定义或可获取的变量.函数等.位于最顶端或最外层的上下文称为全局上下文(global context),全局上下文取决于执行环境,如Node中的global和Browser中的window: 需要注意的是,上下文与作用域(scop

  • javascript执行环境及作用域详解

    最近在重读<javascript高级程序设计3>,觉得应该写一些博客记录一下学习的一些知识,不然都忘光啦.今天要总结的是js执行环境和作用域. 首先来说一下执行环境 一.执行环境         书上概念,执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象.环境中定义的所有变量和函数都保存在这个对象中.虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它. 执行环境是一个概念,一种机制,它定义了变量或函数是否有权访

  • 谈一谈js中的执行环境及作用域

    最近在面试时被问到了对作用域链的理解,感觉当时回答的不是很好,今天就来说说js中的作用域链吧. 首先来说说js中的执行环境,所谓执行环境(有时也称环境)它是JavaScript中最为重要的一个概念.执行环境定义了变量或函数有权访问的其他数据 ,决定了它们各自的行为.而每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中. 理解了执行环境,现在就看看什么是作用域链吧.每个函数都有自己的执行环境,当代码在执行环境中执行时,就会创建变量对象的作用域链.作用域链保证了对执

  • JS函数本身的作用域实例分析

    本文实例讲述了JS函数本身的作用域.分享给大家供大家参考,具体如下: 函数本身也是一个值,也有自己的作用域.它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在作用域无关. var a = 1 var x = function() { console.log(a) } function f() { var a = 2 x() } f() 上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2. 总之,函数执行时所在的作用域

  • JS中的变量作用域(console版)

    作用域说明:指一个变量的作用范围 1.全局作用域 (1) 全局作用域在页面打开时被创建,页面关闭时被销毁 (2) 编写在script标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到 (3) 在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用 (4) 全局作用域中声明的变量和函数会作为window对象的属性和方法保存 var a = 10; b = 20; function an(){ console.log('an') } var bn = fun

  • JavaScript中的执行环境和作用域链

    前言 JS 中的执行环境和作用域链是非常重要的概念,它们是 JS 引擎在处理 JS 代码的时候对变量和函数的处理方式,这两个概念的正确理解能够帮助我们更好地理解和预测代码的行为. 执行环境 执行环境定义了变量或者函数有权访问的数据集合,每一个执行环境都有一个与之关联的变量对象,该执行环境中定义的所有变量和函数都保存在这个对象中.我们无法直接访问这个对象,这个对象只是在解析器处理数据的时候使用. 我们平时说的全局变量就是在最外围的一个执行环境中定义的变量,全局执行环境根据 ECMAScript 的

随机推荐