【.com原稿】前言JavaScript中有一个特性叫做作用域。虽然对于很多开发新手来说,作用域的概念并不是很容易理解。在这篇文章中,我会尽量用最简单的方式来解释作用域和作用域链。希望你有所收获!作用域(Scope)1.什么是作用域作用域是变量、函数和对象在运行时代码的某些特定部分的可访问性。换句话说,范围决定了代码块中变量和其他资源的可见性。可能这两句话不太好理解,我们先看一个例子:functionoutFun2(){varinVariable="Innervariable2";}outFun2();//必须先执行这个函数,否则不知道是什么insideconsole.log(inVariable);//UncaughtReferenceError:inVariableisnotdefined从上面的例子,你可以理解作用域的概念。变量inVariable没有声明在全局范围内,所以在全局范围内取值会报错。我们可以这样理解:作用域是一个独立的站点,这样变量就不会泄露或暴露。也就是说,作用域最重要的用途就是隔离变量,不同作用域的同名变量不会发生冲突。在ES6之前,JavaScript没有块级作用域,只有全局作用域和函数作用域。ES6的到来为我们提供了“块级范围”,这可以通过添加命令let和const来体现。2.全局作用域和函数作用域代码中任何地方都可以访问的对象,都具有全局作用域。一般来说,以下几种情况具有全局作用域:最外层函数和定义在最外层函数外的变量具有全局作用域varoutVariable="我是最外层变量";//最外层变量functionoutFun(){//最外层函数varinVariable="Innervariable";functioninnerFun(){//内层函数console.log(inVariable);}innerFun();}console.log(outVariable);//我是最外层变量outFun();//内层变量console.log(inVariable);//inVariableisnotdefinedinnerFun();//innerFunisnotdefined所有没有定义直接赋值的变量自动声明为具有全局作用域的functionoutFun2(){variable="未定义的直接赋值变量";varinVariable2="内部变量2";}outFun2();//要先执行这个函数,不然不知道里面是什么对象具有全局范围,window对象的内置属性都具有全局作用域,例如window.name、window.location、window.top等。全局作用域有一个缺点:如果我们写了很多行JS代码,函数中没有包含变量定义,那么它们都在全局作用域中。这样会污染全局命名空间,容易造成命名冲突。//张三写的代码中的vardata={a:100}//李四写的代码中的vardata={x:true}这就是为什么jQuery、Zepto等库的源码会放在(函数(){....})()中。因为放在里面的所有变量都不会泄露和暴露,不会污染外面,也不会影响其他库或JS脚本。这是功能范围的体现。函数作用域是指在函数内部声明的变量。与全局作用域相反,局部作用域通常只能在固定代码段内访问,最常见的是在函数内部。functiondoSomething(){varblogName="boatinginthewaves";functioninnerSay(){alert(blogName);}innerSay();}alert(blogName);//scripterrorinnerSay();//scripterrorscopeishierarchical是的,内部作用域可以访问外部作用域中的变量,反之则不行。让我们看一个例子。用气泡比喻作用域可能更容易理解:***输出结果为2、4、12。气泡1是全局作用域,标识符为foo;气泡2是foo的范围。具有标识符a、bar、b;气泡3是作用域bar并且只有标识符c。3、块级作用域ES6开始增加块级作用域,可以通过增加let和const命令来体现,如下:if(true){letname='zhangsan'}console.log(name)//reportanerror,因为let定义的名字在if的块级作用域内,值得注意的是:块语句(大括号“{}”之间的语句),比如if和switch条件语句或者for和while循环语句,不像函数,它们不会创建新的作用域。在块语句中定义的变量将保留在它们已经存在的范围内。if(true){//'if'条件语句块不会创建新作用域varname='Hammad';//name还在全局作用域}console.log(name);//logs'Hammad'作用域链1.什么是自由变量首先,我们来了解一下什么是自由变量。在下面的代码中,console.log(a)需要获取一个变量,但是a在当前范围内没有定义(比较b)。当前范围内未定义的变量,this成为自由变量。如何获取自由变量的值——从父作用域中寻找(注意:这种说法并不严谨,下面会解释)。vara=100functionfn(){varb=200console.log(a)//这里的a是自由变量console.log(b)}fn()2.如果没有parent,作用域链是什么?然后一层层往上找,直到全局作用域都找到或者没有找到,然后放弃。这种逐层关系就是作用域链。vara=100functionF1(){varb=200functionF2(){varc=300console.log(a)//自由变量,顺着作用域链寻找console.log(b)//自由变量,顺着作用域链寻找console.log(c)//本作用域中的变量}F2()}父作用域中的F1(),事实上,有时这种解释可能会产生歧义。fn函数中的varx=10functionfn(){console.log(x)}functionshow(f){varx=20(function(){f()//10,not20})()}show(fn),取时自由变量x的值,应该取在哪个范围内?——要取自创建fn函数的作用域,不管fn函数会在什么地方被调用。所以,停止使用上面的语句。相比之下,用这句话来形容会更合适:“转到创建这个函数的域”。范围内的价值,这里强调的是“创造”而不是“召唤”。记住——其实,这就是所谓的“静态作用域”。vara=10functionfn(){varb=20functionbar(){console.log(a+b)//30}returnbar}varx=fn(),b=200x()//bar()fn()返回bar函数和将其分配给x。执行x(),即执行bar函数的代码。取b值时,直接在fn的范围内取出。取a的值的时候,尝试在fn的范围内取,但是取不到,所以只能在创建fn的范围内查找,找到了,所以最后的结果是30。范围和执行上下文很多开发者经常混淆作用域和执行上下文的概念,我误以为它们是同一个概念,但事实并非如此。我们知道JavaScript是一种解释型语言,JavaScript的执行分为解释和执行两个阶段。解释阶段:词法分析语法分析作用域规则确定执行阶段:创建执行上下文执行函数代码垃圾收集上面我们提到作用域只是一个“站点”,一个抽象的概念,其中没有变量。通过作用域对应的执行上下文来获取变量的值。一个范围可能包含多个上下文。可能从来没有上下文(函数从未被调用过);可能有过,现在调用函数后上下文被销毁;可能同时有一个或多个(关闭)。在同一个作用域下,不同的调用会产生不同的执行上下文,进而产生不同的变量值。范围规则是在JavaScript解释阶段确定的,所以范围是在定义函数时确定的,而不是在调用函数时确定的。scope和executioncontext最大的区别是:executioncontext是在运行时确定的,随时可能改变;范围在定义时确定并且不会改变。vara=1;//globalscopefunctionfn1(){vara=2;//fn1scope}如上面代码,scope表示声明的变量或函数的访问范围,变量a的使用范围fn1会从当前作用域开始查找,如果没有则在上层作用域查找。this.a=1;//全局执行上下文functionfn1(){this.a=2;//fn1执行上下文}varobj=newfn1();如上面代码中,newfn1()的执行上下文是obj可见,作用域和执行上下文不是一个概念,它们的区别就像访问a和this.a时的区别。参考文章深入理解javascript原型和闭包系列Web前端面试指南及高频试题解析深入理解JS中的declarationpromotion、scope(chain)和this关键字JavaScript进阶开发:理解JavaScript作用域和scopechainJavaScriptfunctionScope和scopechainjavascriptscope和executioncontext的区别乘风破浪,硕士生,专注于前端。个人公众号:《前端工匠》,致力于打造一系列适合初中级工程师快速吸收的优质文章!【原创稿件,合作网站转载请注明原作者和出处为.com】
