本文试图解释Javascript中上下文和作用域背后的机制,主要涉及执行上下文、作用域链、闭包、this等概念。执行上下文执行上下文(简称context)决定了Js执行过程中可以获得哪些变量、函数、数据。一个程序可以分成许多不同的上下文。每个上下文都绑定了一个变量对象(variableobject),它就像一个容器,用来存放当前上下文中所有已定义或可访问的变量、函数等。最顶层或最外层的上下文称为全局上下文,全局上下文依赖于执行环境,比如Node中的global,Browser中的window:需要注意的是,context不同于scope的概念。js本身就是一个单进程。每当执行一个函数时,都会生成一个新的上下文。这个上下文会被压入Js上下文栈(contextstack),函数执行完成后弹出。因此,Js解释器总是在栈顶上下文中执行。生成新上下文时,首先绑定上下文的变量对象,包括函数中定义的参数和变量;然后创建一个属于context的作用域链(scopechain),将this赋值给this一个函数所属的Object,这个过程可以用下图来表示:该功能属于。具体来说,当函数定义在全局对中时,this指向全局;当函数用作对象方法时,this指向对象:varx=1;varf=function(){console.log(this.x);}f();//->1varff=function(){this.x=2;console.log(this.x);}ff();//->2x//->2varo={x:"o'sx",f:f};o.f();//"o'sx"文章中提到,当函数执行时产生新的上下文时,会先绑定当前上下文的变量对象,然后再创建作用域链。我们知道函数的定义可以嵌套在其他函数创建的上下文中,也可以在同一个上下文(比如global)中并行定义。作用域链实际上将所有嵌套定义上下文所绑定的变量对象从下往上连接起来,使嵌套函数可以“继承”上层上下文的变量,并行函数之间进行交互。不要干涉:varx='global';functiona(){varx="a'sx";functionb(){vary="b'sy";console.log(x);};b();}functionc(){varx="c'sx";functiond(){console.log(y);};d();}a();//->"a'sx"c();//->ReferenceError:yisnotdefinedx//->"global"y//->ReferenceError:yisnotdefinedClosure如果理解了上面提到的上下文和作用域链的机制,闭包的概念就会很清晰。每个函数在调用时都会创建一个新的上下文和作用域链,作用域链就是将绑定到外(上)上下文的变量对象一一连接起来,使得当前函数可以获取到外层上下文的变量,data等。如果我们在function中定义一个新的函数,同时将内层函数作为一个值返回,那么内层函数包含的作用域链也会一起返回,即使内层函数是在另一个上下文中执行的,它的内部作用域链仍然保持着原来的数据,当前上下文可能无法获取到原来外部函数中的数据,从而保护了函数内部的作用域链,从而形成了“闭包”。请参阅以下示例:varx=100;varinc=function(){varx=0;returnfunction(){console.log(x++);};};varinc1=inc();varinc2=inc();inc1();//->0inc1();//->1inc2();//->0inc1();//->2inc2();//->1x;//->100执行流程如图下面,inc内部返回的匿名函数生成的作用域链包括xininc。即使赋值给inc1和inc2,在全局上下文下直接调用,它们的作用域链还是由定义决定的。上下文环境决定的,由于x是在函数inc中定义的,所以不能被外层的全局上下文改变,从而达到闭包的效果:闭包中的this我们已经多次提到,执行上下文和作用域其实是传递给函数的created和divided,函数中的this不同于作用域链。由函数执行时当前的Object环境决定。这也是this最容易被混淆和误用的地方。一般的例子如下:varname="global";varo={name:"o",getName:function(){returnthis.name}};o.getName();//->"o"由于o执行.getName()时,getName绑定的this就是调用它的o,所以此时this==o;闭包条件下更容易混淆:varname="global";varoo={name:"oo",getNameFunc:function(){returnfunction(){returnthis.name;};}}oo.getNameFunc()();//->"global"此时调用闭包函数返回后相当于:getName=oo。getNameFunc();getName();//->"global"更明显的例子:varooo={name:"ooo",getName:oo.getNameFunc()//此时绑定了闭包函数的thistonewObject};ooo.getName();//->"ooo"当然有时候为了避免这个在闭包中执行时被替换,可以采用如下方法:varname="global";varoooo={name:"ox4",getNameFunc:function(){varself=this;returnfunction(){returnsself.name;};}};oooo.getNameFunc()();//->"ox4"或调用时强制定义执行的对象:varname="global";varoo={name:"oo",getNameFunc:function(){returnfunction(){returnthis.name;};}}oo.getNameFunc()();//->"全球"oo.getNameFunc().bind(oo)();//->"oo"总结Js是一门很有意思的语言,因为它的很多特性都是针对HTML中DOM的操作,所以显得随意,稍微不够严谨,但随着前端的不断繁荣发展和Node的兴起,Js不再是jQuery时代的“玩具语言”或“CSS扩展”。本文提到的概念,对于新手或者从传统Web开发过渡过来的Js开发者来说,是非常容易混淆或者误解的。希望这篇文章能帮到你syntaxcheatsheet不涉及更深层次的闭包、作用域等,但没想到这个项目竟然获得了3000多颗star,所以不能虎头蛇尾,上文。
