当前位置: 首页 > Web前端 > JavaScript

深入理解JavaScript——词法环境

时间:2023-03-27 12:52:14 JavaScript

前言在谈一个概念之前,我们需要先确定它的前提。本文基于ECMAScript5写了一句话解释词法环境是记录JavaScript代码编译阶段的变量声明、函数声明、函数。声明形参的集合JavaScript的编译过程在介绍词法环境之前,我们先看一下V8中JavaScript的编译和执行过程,大致可以分为三个阶段。第一步:当V8引擎刚拿到执行上下文时,它会从上到下逐行对代码进行分词/词法分析(Tokenizing/Lexing)。分词是指:如vara=2;这段代码将被分词为:vara2and;这样的原子符号(atomictoken);词法分析指的是:注册变量声明、函数声明、函数声明的形参Step2:分词后会进行代码分析,引擎会将token分析翻译成AST(AbstractSyntaxTree)。在这一步中,如果发现语法错误,会直接报错,不会进行下一步。三步:引擎生成CPU可以执行的机器码。第一步是词法分析,用于注册变量声明、函数声明、函数声明的形参。当后面的代码执行时,它就会知道去哪里获取变量的值。而函数,注册的地方是LexicalEnvironment(词法环境)——深入理解JavaScript-词法环境总结:引擎会先编译JavaScript代码,然后再解释。编译器的部分工作是找到所有声明并将它们与适当的范围相关联。让我们上到万米高空,看看整个JavaScript执行生命周期。JavaScript执行生命周期分为两个阶段,编译阶段和执行阶段编译阶段由编译器完成,它将代码翻译成可执行代码。这个阶段可以知道所有标识符在哪里,它们是如何声明的,以及范围规则。编译阶段进行变量声明。编译阶段变量声明提升,但指未定义编译阶段,所有非表达式函数声明提升。在代码执行阶段,执行可运行代码并生成执行上下文。这部分由引擎完成,引擎负责变量赋值、函数引用和代码执行(PS:对于JavaScript,大多数情况下下一次编译发生在代码执行前的几微秒)。我们要说的词法环境就是编译阶段负责收集的“容器”。注意:JavaScript使用的是词法作用域(staticscope),所以词法环境与我们使用的不同所写代码的结构是对应的,换句话说,我们写的代码是什么,词法环境就是它的样子。词法环境在定义代码时就确定了,与代码在何处被调用无关。什么构成词法环境词法环境的内部由两部分组成:环境记录器(EnvironmentRecord),对外部环境的引用(outer)环境记录器记录存储变量,函数声明,以及对形参的引用函数声明。它可以访问其父词法环境(作用域)环境记录器分为两种类型声明式环境记录(DeclarativeEnvironmentRecord):用于记录由标识符直接定义的元素,如变量、常量、let、class、module、import、和函数声明。ObjectEnvironmentRecord:主要用于with和global的词法环境。其中声明式环境记录(DeclarativeEnvironmentRecord)分为两种:函数环境记录(FunctionEnvironmentRecord):用于函数作用域。模块环境记录(ModuleEnvironmentRecord):模块环境记录用于反映模块的外部作用域,即模块导出所在的环境。下面我们做一个分类图,来更具体的了解词法环境中包含的东西。环境记录器很容易理解。它只不过是变量的集合。什么是外?在上一篇作用域的文章中,我们总结过:JavaScript的作用域是词法作用域,与定义在那里的函数相关,outer是指向词法环境的父词法环境(作用域)。我们举个例子来看看词法环境的构成元素:vara=1;functionfoo(){console.log(a);函数bar(){varb=2;控制台日志(a*b);}酒吧();}functionbaz(){vara=10;富();}巴兹();其词法作用域关系图如下:更具体的关系图如下:我们也可以用伪代码来模拟上面代码的词法环境://全局词法环境GlobalEnvironment={outer:null,//外部环境全局环境的引用为空GlobalEnvironmentRecord:{//全局this绑定指向全局对象[[GlobalThisValue]]:ObjectEnvironmentRecord[[BindingObject]],//声明式环境记录,除了全局函数和var,其他声明都绑定在这里DeclarativeEnvironmentRecord:{},//对象环境记录,绑定对象是全局对象ObjectEnvironmentRecord:{a:1,foo:<>,baz:<>,isNaN:<>,isFinite:<<函数>>,parseInt:<<函数>>,parseFloat:<>,Array:<>,Object:<>,...}}}//foo函数的词法环境fooFunctionEnvironment={outer:GlobalEnvironment,//外部词法环境指的是全局环境FunctionEnvironmentRecord:{[[ThisValue]]:GlobalEnviroment,//thisbinding指向全局环境bar:<>}}//bar函数的词法环境barFunctionEnvironment={outer:fooFunctionEnviroment,//外部词法环境指的是foo函数词法环境FunctionEnvironmentRecord:{[[ThisValue]]:GlobalEnviroment,//thisbinding指向全局环境b:2}}//baz的词法环境functionbazFunctionEnvironment={outer:GlobalEviroment,//externallexicalTheenvironmentreferencepointstotheglobalenvironmentFuntionEnvironmentRecord:{[[ThisValue]]:GlobalEnviroment,//thisbindingpointstotheglobalenvironmenta:10}}我们可以看到词法环境的两个重要组成部分,其中外层是由作用域决定的,环境记录器记录了所有的变量。当在词法环境中找不到该变量时,会引导outer去父词法环境中寻找该变量,这样就形成了我们之前做过的作用域链变量提升和函数提升。也就是说,在编译时,在执行任何代码之前,首先处理包括变量和函数在内的所有声明。当你看到变量a=1;,这可能被认为是一个声明,但JavaScript实际上将其视为两个含义:vara=undefined;和一个=2;.第一个定义语句在编译阶段进行,第二个赋值语句将留在原地等待执行阶段例如:vara=1;varb=true;functionfoo(){console.log(a);}foo();代码执行前,即编译阶段:a=undefined;b=未定义;foo=function(){console.log(a);};执行阶段:a=1;b=真;foo=function(){console.log(a);};函数优先级函数声明和变量声明都被提升。但是这个值得注意的细节是函数优先于变量例如下面的代码:foo();varfoo;functionfoo(){console.log(1);}foo=function(){console.log(2);};

AnswerOutput1insteadofundefinedor2
这段代码会被解释为如下形式:functionfoo(){console.log(1);}//varfoo被忽略foo();//1foo=function(){console.log(2);};注意varfoo出现在functionfoo()的声明之前...但是函数声明优先于变量提升,即使写在函数前面,仍然会以函数的形式显示(变量是忽略)foo();functionfoo(){console.log(1);}varfoo=function(){console.log(2);};functionfoo(){console.log(3);
answeroutput3
关于函数声明和变量声明,我们可以引用很多例子,比如这个例子answerbar2改一下顺序呢:varbar=function(){console.log('bar2');};functionbar(){console.log('bar1');}bar();<详情>回答bar2这些话题本质上都绕不开我们之前说的原则:函数和变量在编译阶段被提升,代码在执行阶段执行到位。编译阶段,函数bar被提升,执行阶段,bar赋值给function(){...},输出结果bar2var,let,const,function等都会被提升(hoist),但是let和const不会被初始化,所以会提前初始化使用报告ReferenceError来总结我们介绍了词法环境,从它是如何产生的,到它是什么(由什么组成),然后到下面理解词法环境的函数和变量是我们下一节-执行上下文和调用栈打下基础的参考资料UnderstandingtheexecutioncontextandexecutionstackinJavaScript深入理解JavaScript-whatJavaScriptismadeof深入理解深入理解JavaScript——一切皆对象深入理解JavaScript——对象(object)深入理解JavaScript——whatnewdoes深入理解JavaScript——对象。深入理解JavaScript的秘密——Prototype深入理解JavaScript——继承深入理解JavaScript——JavaScript中的第一位皇帝深入理解JavaScript——instanceof——寻祖深入理解JavaScript——FunctionIn-深入理解JavaScript-scope深入理解JavaScript-——this关键字深入理解JavaScript——call、apply、bindwill深入理解JavaScript——立即执行函数(IIFE)深入深入理解JavaScript——词法环境深入理解JavaScript——执行上下文和调用栈深入理解JavaScript——函数域VS执行上下文深入理解JavaScript——闭包深入理解JavaScript——反shakeandthrottling深入理解JavaScript——函数式编程深入理解JavaScript——垃圾回收机制深入理解JavaScript的tanding——array深入理解JavaScript——loops来这里深入理解JavaScript——String