本文转载自微信公众号“大千世界”,转载请联系大千世界公众号。在开始之前,有一个问题大家可以一起讨论:JS是解释型语言还是编译型语言?(JS)是一种解释型语言,它有自己的编译器形式,运行在JS引擎中。每个Web浏览器都有自己形式的JS引擎,尽管目的是相同的。Chrome有v8,Mozilla有spidermonkey等,JS引擎只是把JS源码转换成编译器能看懂的语言,然后执行。ExecutioncontextJS代码运行的环境构成了executioncontext,它决定了哪些代码可以访问变量、函数、对象等。1.GlobalExecutionContext代码第一次运行的任何时候,或者代码不在里面的时候任何函数,它进入全局执行上下文。整个代码执行过程中只有一个全局执行上下文。对于浏览器的全局执行上下文,它做了两件事:创建一个窗口对象。将this指向window对象(非严格模式)2.函数执行上下文当一个函数执行时,它会创建一个新的函数执行上下文,可以有任意多个函数执行上下文。执行栈/调用栈浏览器中的JS解析器是单线程的,一次只能做一件事。代码中有一个全局的执行上下文和无数的函数执行上下文,那么它们是按照什么顺序执行的呢?这里需要一个执行上下文栈的概念。JS引擎通过创建执行上下文栈来管理执行上下文。这里,执行上下文栈可以描述为一个存储函数调用的栈结构,执行顺序遵循先进后出的原则,也就是说一个函数的执行上下文会从执行上下文栈中移除函数执行后。每当在浏览器中加载脚本时,堆栈中的第一个元素就是全局执行上下文。但是,当函数执行时,会创建一个执行上下文并将其虚拟放置在全局执行上下文之上。一旦函数完成执行,它就会从执行堆栈中弹出并将控制权传递给它下面的上下文。我们举个例子来模拟一下上面的过程:第一步:当上面的代码被加载到浏览器中时,JS引擎会创建一个全局执行上下文(globalexecutioncontext)并将其压入当前执行栈。第二步:假设func1()调用是最后执行的,那么JS引擎会为函数创建一个新的执行上下文(functionexecutioncontext),并将其推到全局执行上下文之上。第三步:在func1()中,我们调用了func2(),因此JS引擎为该函数创建了一个新的执行上下文,并将其推到func1()执行上下文之上。第四步:当func2()函数结束时,将其执行上下文从当前栈中弹出,控制权交给其下方的执行上下文,即func1()函数的执行上下文。第五步:当func1()函数结束时,它的执行栈从栈中移除,控制权交给全局执行上下文。所有代码执行完毕后,JS引擎会从当前栈中移除全局执行上下文。执行上下文阶段执行上下文有两个主要阶段。CreationExecution1.Creationstage函数创建时要做三件事:(1)首先,为作用域链中的每个函数或变量建立与外部环境的连接。告诉执行上下文它应该包含什么,以及它应该在哪里寻找方法来解析函数引用和变量值。对于全局环境,外部环境为空。但是,全局环境中的所有环境都以全局环境作为其外部环境。例如:如果函数a包含在函数b中,则说明a有一个外部环境b。(2)接下来,通过扫描动作链,创建一个环境记录,其中全局上下文(网络浏览器中的窗口)、变量、函数和函数参数的创建和引用都在内存中完成。(3)最后在第一步创建的每个执行上下文中判断this的值(对于全局执行上下文,this指向window)。因此,我们可以将创建阶段表示为创建阶段={scopeChain:{/*作用域链分析*/},variableObject:{/*arguments,functionparameters,internalvariables,etc.*/},this:{},}variableObject:初始化函数的参数variableObject,促进函数声明和变量声明。scopeChain:在执行上下文的创建阶段,作用域会在创建变量对象后创建。作用域链本身包括变量对象。作用域负责解析变量。当要求解析变量时,它会从代码嵌套结构的最内层开始。如果在最内层没有找到对应的变量,就会依次在父作用域中查找,直到找到最外层作用域。this:判断this的方向。这里需要注意的是this的值是在执行的时候确定的,而不是在定义的时候确定的。ExecutionPhase这是代码开始在Create阶段形成的执行上下文中运行的阶段,变量值逐行赋值。在执行开始时,JS引擎在其创建阶段对象中查找对已执行函数的引用。如果在自己的范围内找不到,就会一直往上找,直到到达全局环境。如果在全局环境中找不到引用,它将返回错误。但是,如果找到一个引用,并且该函数正确执行,那么该特定函数的执行上下文将从堆栈中弹出,JS引擎将继续执行下一个函数,它们的执行上下文将被添加到堆栈并执行,依此类推。让我们通过示例查看上述两个阶段以更好地理解它。在创建阶段,全局执行上下文如下所示:globalExecutionObj={outerEnvironmentConnection:null,variableObjectMapping:{name:uninitialized,title:undefined,date:uninitialized,func1:func,},this:window//GlobalObject}**注意:**上面,let(name)和const(date)定义的变量在创建阶段没有任何关联值,但是var(title)定义的变量会被设置为undefined。这就是为什么我们可以在声明之前访问var**定义的变量(虽然它们没有定义)**,但是当我们在声明let和const变量之前访问它们时,我们会得到引用错误。这就是我们所说的变量提升,即所有使用var的变量声明都被提升到其局部作用域(在函数内部声明)或全局作用域(在函数外部声明)的顶部。在执行阶段,完成变量赋值。所以全局执行上下文在执行阶段类似于下面这样:,this:window//GlobalObject}**注意:**在执行阶段,如果JS引擎在源码中找不到声明位置的let变量的值,那么就会赋值undefined。现在,当func1被执行时,会生成一个新的函数执行上下文,它的创建阶段类似于:func1ExecutionObj={outerEnvironmentConnection:Global,variableObjectMapping:{arguments:{0:10,length:1},num:10、author:undefined,val:uninitialized,func2:undefinedfixed:uninitializedaddFive:pointertofunctionaddFive()},this:GlobalObjectorundefined}在执行阶段类似如下:func1ExecutionObj={outerEnvironmentConnection:Global,variableObjectMapping:{arguments:{0:10,length:1},num:10,author:"Deepak",val:3,func2:pointertofunctionfunc2()fixed:"Divine"addFive:pointertofunctionaddFive()},this:GlobalObjectorundefined}函数执行后,global环境会更新。然后全局代码完成,程序结束。作用域和作用域链1.作用域JavaScript中作用域分为三种:全局作用域函数作用域块级作用域(ES6)作用域最大的作用是隔离变量或函数,控制它们的生命周期。范围是在创建函数执行上下文时定义的,而不是在执行函数时定义的。2.什么是作用域链当一个块或函数嵌套在另一个块或函数中时,就会发生作用域嵌套。在当前函数中,如果JS引擎找不到某个变量,就会在上层嵌套作用域中查找,直到找到该变量或者到达全局作用域。这样的链式关系就成为作用域链(ScopeChain)。下面举个例子来演示一下:varscope='global';functioncheckscope(s){varscope='localscope';functionf(){returnscope;}returnf();}checkScope('scope')首先声明checkscope函数时,内部[[scope]]的一个内部属性将被绑定:checkscope.[[scope]]=[globalContext.VO];然后,在执行checkScope函数之前,创建执行上下文checkscopeContext并将其压入执行上下文栈:Assignmentfunction[[scope]]attributeinitializationscopechaincreatevariableobjectpushvariableobjectintothetopofscopechain//->初始化作用域链;checkscopeContext={scope:[globalContext.VO],}//->创建变量对象checkscopeContext={scope:[globalContext.VO],VO={arguments:{0:'scope',length:1,},s:'scope',//传入参数f:functionf(),scope:undefined,//此时声明的变量未定义},}//->将变量对象推入作用域链的顶端checkscopeContext={scope:[VO,globalContext.VO],VO={arguments:{0:'scope',length:1,},s:'scope',//传入参数f:functionf(),scope:undefined,//此时声明的变量未定义},}在执行阶段,修改变量对象内部对应字段的值:checkscopeContext={scope:[VO,globalContext.VO],VO={arguments:{0:'scope',length:1,},s:'scope',//传入参数f:pointertofunctionf(),scope:'localscope',//变量赋值}}在代码执行阶段,会看到声明代码f函数,并将[[scope]]属性绑定到f函数:f.[[scope]]=[checkscopeContext.VO,//f函数作用域还包括checkscope的变量对象globalContext.VO];文到此结束,希望对大家有所帮助
