javascript代码解析过程执行上下文和作用域是javascript非常重要的部分。要了解它们,首先要说说javascript的运行机制。通过以下步骤解析javascript代码。Parser模块将javascript源代码解析为抽象语法。树(AST)Ignition模块将抽象语法树编译成字节码(byteCode),再编译成机器码。当函数被多次执行时,Ignition会记录优化信息,而Turbofan直接将抽象语法树全局编译成机器码Context在理解了上面的javascript运行机制之后,我们来看看下面全局代码控制台的执行方式。log(user)varuser='alice'varnum=16console.log(num)上面的代码是通过以下步骤执行的javascript->ast全局创建一个GO(GlobalObject)对象。GO中内置了很多模块,比如Math、String、window属性,其中window的值为this,即自身GO对象全局定义的变量user和num,会加上GO对象,并赋值asundefinedast-->IgnitionV8引擎执行代码时,有调用栈(ECStack),此时创建全局上下文栈(GlobalExcutionContext)GEC存在VO(variableObject),指向GO对象Ignition在全局上下文中-->运行结果通过VO找到GO,将user分配给alice,将num分配给16如下图,当value为undefined打印user时,并没有执行user的赋值语句,所以user的值还是undefined。打印num的时候,已经执行了对num赋值的语句,所以num的值为16。函数上下文定义函数时,执行方式和全局的有点区别varname='alice'foo(12)functionfoo(num){console.log(m)varm=10varn=20console.log("foo")}上面的代码已经通过了接下来就是执行javascript-->ast全局创建一个GO(GlobalObject)对象。GO中内置了很多模块,比如Math、String、windowproperties。window的值就是this,也就是自己的GO对象的全局定义。变量名称将被添加到GO对象并分配一个未定义的值。函数foo会开辟一块内存空间,比如0x100,用来存放父作用域和自己的代码块。函数foo的父作用域是全局对象GO将foo添加到GO对象中并分配为内存地址,比如0x100ast-->IgnitionV8引擎执行代码时,有一个调用栈(ECStack),并且此时创建全局上下文栈(GlobalExcutionContext)GEC存在VO(variableObject),指向全局上下文中的GO对象Ignition-->运行结果(1)执行全局代码,通过VO找到GO,赋值给alice在执行函数foo之前的名字,创建一个函数执行上下文(FunctionExcutionContext),有VO指向AO对象创建ActivationObject,定义num和m都为undefined(2)执行函数并将num赋值为12,m为10,n为20。函数foo执行后,从调用栈(ECStack)的顶部弹出,如下所示。所以上面代码执行的结果是未定义的。预编译。Parser模块在将javascript源码编译成AST的时候,也会经过一些详细的步骤。Stram将源代码处理成统一的编码格式。Scanner进行词法分析,将代码转换为tokentoken,token将转换为AST,通过preparer和parser模块parser用于解析全局定义的函数和变量,函数中定义的函数只会被预解析。Preparser闭包的执行顺序varuser="alice"foo(12)functionfoo(num){console.log(m)varm=10functionbar(){console.log(user)}bar()}的上面的代码在以下步骤之后执行javascript-->ast全局创建一个GO(GlobalObject)对象,GO中有很多内置的模块,比如Math、String、window属性,其中window的值为this,也就是将自身GO对象全局定义的变量user添加到GO对象,并赋值为undefinedfunctionfoo会开辟一块内存空间,比如0x100,用来存放父作用域(parentscope)和自己的代码块。函数foo的父作用域是全局对象GO。将foo添加到GO对象中,赋值给内存地址,比如0x100ast-->IgnitionV8引擎执行代码时,有一个调用栈(ECStack)。此时全局上下文栈(GlobalExcutionContext)GEC存在于VO(variableObject)中,指向全局上下文中的GO对象Ignition-->运行结果(1)执行全局代码通过VO查找GOandassignusertoalice在执行functionfoo之前,创建foo函数的执行上下文(FunctionExcutionContext),有VO(variableObject)指向AO(ActivationObject)对象来创建ActivationObject,setnum,m都定义了asundefined为函数bar开辟0x200内存空间用于存放父作用域和自身代码,bar的父作用域为函数foo的作用域AO+全局作用域GO将bar添加到foo的AO对象中,assignment是内存地址,0x200(2)执行函数foo将num赋值给12,m赋值给10(3)执行函数bar创建bar的执行上下文,有VO(VariableObject)指向AO(ActivationObject)对象创建ActivationObject,此时,AO是一个空对象。函数bar被执行,从调用栈(ECStack)顶部弹出的函数foo也被执行。它是从调用栈(ECStack)的顶部弹出的,所以上面代码的执行结果是undefinedalicem打印的时候还没有赋值,所以打印user为undefined,先在自己的scope中查找,如果没有找到,则在父作用域foo的AO对象中查找,如果没有找到,则在全局GO对象中查找作用域函数解析成AST(抽象语法树)时确定域,并且与调用它的地方无关varmessage="HelloGlobal"functionfoo(){console.log(message)}functionbar(){varmessage="HelloBar"foo()}bar()上面的代码是在下面的步骤之后执行的javascript-->ast创建一个GO全局(GlobalObject)对象全局定义的变量message会被添加到GO对象中,赋值给未定义的函数foo开辟一块内存空间,即0x100,用于存放父作用域和自身的代码块,函数foo的父作用域是全局对象GO,将foo添加到GO对象中,并赋值为内存地址,0x100处的函数bar在0x200开辟一块内存空间,用于存放父作用域和自身代码块。函数foo的父级作用域是全局对象GO,给GO对象添加bar,赋值给内存地址,0x200ast-->IgnitionV8引擎执行代码时,有一个调用栈(ECStack).此时全局上下文栈(GlobalExcutionContext)GEC存在VO(VariableObject),poi到全局上下文中的GO对象Ignition-->运行结果(1)执行全局代码,通过VO找到GO并将消息赋值为HelloGlobal在执行函数bar之前,创建bar函数的执行上下文(FunctionExcutionContext),里面有一个VO(variableObject)指向一个AO(ActivationObject)对象来创建一个ActivationObject,消息定义为undefined(2)执行函数bar,将消息赋值给HelloBar(3)执行函数foo创建foo的执行上下文,有一个VO(variableObject)指向AO(ActivationObject)对象创建一个ActivationObject。这时候AO打印一个空对象的消息。这个时候,自己的作用域内是没有消息的。查找父作用域。foo的父作用域由GO函数foo完成,从调用栈顶弹出函数bar(ECStack)也执行,最终输出结果从调用栈顶弹出(ECStack))HelloGloabl的图示如下混淆点1.没有通过var标识符声明的变量会被添加到全局varn=100functionfoo(){n=200}foo()console.log(n)的执行过程如下javascript-->在astGO对象中定义n为undefined开辟foo函数0x100的内存空间,父作用域为GO将foo添加到GO对象中,值为0x100ast-->Ignition创建一个全局的context,VO指向GO执行foo函数在创建函数context之前,VO对象指向AO对象创建AO对象,AO为空对象赋值。GO中的变量n被赋值为100,执行foo函数中的赋值。因为没有var标识符声明,所以直接赋值给了全局的GO。n被赋值为200,所以此时执行结果为200。2.如果函数作用域中有变量,则不会在父作用域中查找functionfoo(){console.log(n)varn=200console.log(n)}varn=100foo()的执行顺序如下javascript-->给astGO对象添加变量n,值为undefined为函数foo开辟内存空间0x300,父作用域为GO给GO对象添加foo,值为0x300ast--->Ignition创建一个全局上下文,VO指向GO在执行函数foo之前创建一个函数上下文,VO指向AO创建一个AO对象,添加变量n,赋值给undefined,将GO中的n赋值为100,执行foo,打印n,此时先在自己的作用域中查找是否有变量n。AO中n的值未定义,因此您不会在父作用域中进行搜索。将AO中n的值赋给200并打印n。此时你自己的scope中有n,值为200,所以执行结果为undefined2003.return语句不影响ast的生成在代码解析阶段,不会受到return的影响陈述。在ast生成过程中,只会查找var和函数标识符定义的内容vara=100functionfoo(){console.log(a)returnvara=100console.log(a)}的执行过程foo()如下javascript-->astGO对象中定义a为undefined开辟foo函数0x400的内存空间,父作用域为GO。将foo添加到GO对象中,值为0x400ast-->Ignition创建全局上下文,VO在执行foo函数前指向GO。函数上下文,VO对象指向AO对象创建AO对象,给AO对象添加a,赋值undefined。GO中的变量a赋值为100,执行foo函数,打印a。此时a没有定义,所以输出undefined执行return,return之后的代码不会执行,所以执行结果是undefined四、即使赋值vara=b=10,相当于vara=10;b=10functionfoo(){vara=b=10}foo()console.log(b)console.log(a)执行过程如下javascript-->ast创建一个GO对象,GO对象为empty开辟foo函数0x500的内存空间,父作用域为GO并将foo添加到GO对象中,值为0x500ast-->Ignition创建全局上下文,VO在执行foo函数前指向GO,创建函数上下文,VO对象指向AO对象创建AO对象,将a添加到AO对象,赋值给undefined执行foo函数,vara=b=10,相当于vara=10;b=10,一个变量有标识符,所以a被加到AO对象中,赋值10,b没有标识符,所以b被加到全局对象GO中,赋值10打印b,b可以在GO对象中找到,打印a的值为10,GO对象中没有a,也没有父作用域,所以无法向上查找。此时报错,所以执行结果为10UncaughtReferenceError:aisnotdefined以上就是具体介绍如何从javascript代码解析过程中理解执行上下文和作用域提升。关于高级js,开发者需要掌握的东西还是很多的。可以看看我写的其他博文,持续更新中~
