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

FE.ES-UnderstandingECMAJavascriptScope

时间:2023-03-31 00:03:02 CSS

本文仅将所学内容整理为笔记,如有错误请指正。作用域作用域是一组规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是为变量赋值,则使用LHS查询;如果目的是获取变量的值,则使用RHS查询。赋值运算符导致LHS查找。=运算符或调用带有参数的函数将导致对关联范围的赋值。JavaScript引擎首先编译代码才能执行,在此过程中,像vara=2这样的声明被分解为两个独立的步骤:首先,vara在其范围内声明新变量。这发生在一开始,在代码执行之前。接下来,a=2查找(LHS查找)变量a并赋值给它。LHS和RHS查询都是从当前执行作用域开始,如果有必要(即没有找到需要的标识符),会继续在上层作用域中查找目标标识符,这样每次提升作用域一层(一层),最后到达全局范围(顶层),不管找到与否都会停止。不成功的RHS引用会导致抛出ReferenceError异常。不成功的LHS引用会导致自动和隐式创建一个全局变量(在非严格模式下),该变量使用LHS引用的目标作为其标识符,或引发ReferenceError异常(在严格模式下)。词法范围意味着范围由编写代码时声明函数的位置决定。编译的词法分析阶段基本上知道所有标识符在何处以及如何声明,因此可以预测在执行期间将如何查找它们。JavaScript中有两种机制可以“欺骗”词法作用域:eval(..)和with。前者可以评估包含一个或多个声明的“代码”字符串,从而修改现有的词法范围(在运行时)。后者通过将对象引用视为作用域并将对象属性视为作用域中的标识符,实质上创建了一个新的词法作用域(同样是在运行时)。这两种机制的一个副作用是引擎无法在编译时优化范围查找,因为引擎只能谨慎地认为这种优化无效。使用这些机制中的任何一种都会导致您的代码运行速度变慢。不要使用它们。因为JavaScript使用词法范围,所以函数的范围是在定义函数时确定的。varscope="globalscope";functioncheckscope(){varscope="localscope";函数f(){返回范围;}returnf();}checkscope();//localscopevarscope="globalscope";functioncheckscope(){varscope="localscope";函数f(){返回范围;}returnf;}checkscope()();//localscopevarscope="globalscope";functioncheckscope(){varscope="localscope";函数f(){返回范围;}returnf;}varfoo=checkscope();foo();//局部作用域函数表达式和函数声明函数声明:函数函数名(参数:可选){函数体}函数表达式:函数函数名(可选)(parameters:optional){functionbody}判别:如果一个函数名没有声明,那么它一定是一个表达式。如果functionfoo(){}是赋值表达式的一部分,则它是一个函数表达式;如果它包含在函数体内或程序函数声明的最顶部,则它是一个functionfoo(){}。被括号(functionfoo(){})括起来,之所以他是一个表达式,是因为括号()是一个分组运算符,它的内部只能包含表达式functionfoo(){}//语句,因为它是程序的一部分(function(){functionbar(){}//声明,因为它是函数体的一部分})();varbar=functionfoo(){};//表达式,因为它是赋值表达式的一部分newfunctionbar(){};//expression因为它是一个新的表达式(functionfoo(){});//表达式:包含在分组运算符中try{(varx=5);//分组运算符只能包含表达式,不能包含语句:varhereisastatement}catch(err){//SyntaxError}虽然可以在条件语句中使用函数声明,但它们并不规范。善用函数表达式函数声明覆盖变量声明,但不是变量赋值functionvalue(){return1;}varvalue;alert(typeofvalue);//"function"函数作用域和块作用域函数是JavaScript中最常见的作用域单元。本质上,在函数内部声明的变量或函数在封闭范围内是“隐藏”的,这是良好软件设计的有意原则。但是函数并不是作用域的唯一单位。块作用域是指变量和函数不仅可以属于它们所在的作用域,还可以属于一个代码块(通常在{..}内)。从ES3开始,try/catch构造在catch子句中具有块作用域。let关键字(var关键字的表亲)在ES6中引入,用于在任意代码块中声明变量。如果(..){让a=2;}声明一个劫持if的{..}块的变量,并将该变量添加到块中。有些人认为块作用域不应完全用作函数作用域的替代品。这两个功能应该同时存在。开发人员可以而且应该根据自己的需要选择使用哪个范围来创建可读和可维护的代码。推广我们习惯于认为vara=2;作为声明,但JavaScript引擎并不这么看。它将vara和a=2视为两个单独的声明,第一个是编译阶段的任务,而第二个是执行阶段的任务。这意味着无论声明出现在作用域的哪个位置,它都会在代码本身被执行之前首先被处理。这个过程可以形象化为所有声明(变量和函数)将被“移动”到它们各自范围的顶部,这个过程称为提升。声明本身会被提升,但赋值(包括函数表达式的赋值)不会被提升。注意避免重复声明,尤其是普通的var声明和function声明混在一起的时候,否则会造成很多危险的问题!vara;if(!("a"inwindow)){a=1;}alert(a);作用域闭包通常,程序员会错误地认为只有匿名函数才是闭包。其实并不是这样,正如我们所看到的——正是因为作用域链,所有的函数都是闭包(不管函数类型:匿名函数,FE、NFE、FD都是闭包),只有一个类函数除外,类函数是通过Function构造函数创建的函数,因为它的[[Scope]]只包含全局对象。为了更好的阐明这个问题,我们对ECMAScript中的闭包做了两种定义(即两种闭包):在ECMAScript中,闭包是指:从理论上看:所有的函数。因为它们在创建的时候都保存了上层上下文的数据。即使对于简单的全局变量也是如此,因为在函数中访问全局变量等同于访问自由变量,此时使用的是最外层作用域。从实用的角度来看:以下函数被认为是闭包:即使创建它的上下文已经被销毁,它仍然存在(例如,内部函数从父函数返回)并且自由变量循环闭包在代码中引用for(vari=1;i<=5;i++){(function(j){setTimeout(functiontimer(){console.log(j);},j*1000);})(i);}for(vari=1;i<=5;i++){让j=i;//是的,闭包的块作用域!setTimeout(functiontimer(){console.log(j);},j*1000);}for(leti=1;i<=5;i++){setTimeout(functiontimer(){console.log(i);},i*1000);}vardata=[];for(vari=0;i<3;i++){data[i]=function(){console.log(i);};}data[0]();//3data[1]();//3data[2]();//3个modulesmodules有两个主要特点:(1)调用wrapper函数创建一个内部scope;(2)包装函数的返回值必须至少包含一个对内部函数的引用,这会创建一个覆盖包装函数整个内部作用域的闭包。现代模块机制varMyModules=(functionManager(){varmodules={};functiondefine(name,deps,impl){for(vari=0;ie:undefined};*/作用域链(scopechain)和[[scope]]属性Scope=AO|VO+[[Scope]]Examplevarx=10;函数foo(){变量y=20;函数bar(){varz=30;警报(x+y+z);}酒吧();}foo();//60全局上下文的变量对象为:globalContext.VO===Global={x:10foo:};创建“foo”时,[[“foo”的范围]]属性为:foo.[[Scope]]=[globalContext.VO];在“foo”激活(进入上下文)时,“foo”上下文的活动对象是:fooContext.AO={y:20,bar:};“foo”上下文的范围链是:fooContext.Scope=fooContext.AO+foo.[[Scope]]//即:fooContext.Scope=[fooContext.AO,globalContext.VO];当创建内部函数“bar”时,它的[[scope]]是:bar.[[Scope]]=[fooContext.AO,globalContext.VO];当“bar”被激活时,“bar”上下文的活动对象是:barContext.AO={z:30};“bar”上下文的范围链是:barContext.Scope=barContext.AO+bar.[[Scope]]//即:barContext.Scope=[barContext.AO,fooContext.AO,globalContext.VO];“x”、“y”和“z”的标识符解析如下:-"x"--barContext.AO//notfound--fooContext.AO//notfoundglobalContext.VO//found-10-"y"--barContext.AO//未找到fooContext.AO//已找到-20-"z"barContext.AO//已找到-30参考资料:深入理解JavaScript系列(16):关闭ClosuresJavaScriptDeep执行上下文堆栈JavaScript深度词法作用域和动态你不懂的作用域JS:作用域与闭包变量对象(Variableobject)深入理解JavaScript系列(十四):作用域链(ScopeChain)会让声明提升?