这篇文章介绍了JavaScript中的作用域。先说什么是作用域,介绍一下JavaScript的词法作用域,然后从词法作用域讲到动态作用域,并比较两者。说完scope的分类,我们再用一个例子来说明scope的作用。然后说说scope的分类以及函数scope因为有特点怎么写?编译原理一段程序中的源代码在执行前会经过三个步骤,统称为“编译”Tokenizing/Lexing比如vara=2;被拆解成最基本的词法单元var,a,=2。解析/文法分析(Parsing)将词法单元流(数组)转化为代表程序语法结构的数字,由原生的逐级嵌套组成。这棵树叫做“抽象语法树”(AST,这也是现代前端框架的关键)CodeGeneration将AST转换为可执行代码的过程称为代码生成-《你不知道的JavaScript上卷》简单的说:任意JavaScript代码片段编译并理解作用域变量赋值操作在执行前会执行两个动作。首先,编译器会在当前范围内声明一个变量(如果之前没有声明过),然后引擎会在运行时在范围内声明它。查找变量,如果可以找到它,则为其赋值Scope嵌套作用域是一组用于按名称查找变量的规则当一个块或函数嵌套在另一个块或函数中时,就会发生作用域嵌套。因此,当在当前作用域中找不到变量时,引擎会继续在外层嵌套作用域中查找,直到找到该变量,或者到达最外层作用域(即全局作用域)。通过示例了解作用域vara=1;functionfoo(){vara=2;控制台日志(一);varmyFunction=(function(){vara=3;console.log(a);})();}functionbar(){vara=4;foo();}bar();//23将函数翻译成图像如下:每个作用域是一个域,在这个域中你的变量是可以自定义的,可以和外面一样,也可以随便启动,但是当代码执行,先在执行域里找变量,找不到再往外层找,一层层找,直到全局作用域注意,你的代码写的Whereisyourscope?例子中foo函数和bar函数是同级(samelevel),它们里面的变量互不影响。这是JavaScript作用域的词法作用域和动态作用域两种主要的作用域操作模式。第一种是大多数编程语言最常用的词法作用域,另一种称为动态作用域,例如Bash脚本词法作用域是一组引擎如何查找变量以及将在何处找到变量的规则。词法作用域最重要的特点是它的定义过程发生在代码的编写阶段(假设你不使用eval或with),也就是说,你的作用域在你写完之后就固定了。JavaScript没有动态作用域。它只有词法作用域,简单明了,但是这个机制有点类似于动态作用域的主要区别:词法作用域是在编写代码或定义时确定的,而动态作用域是在运行时确定的(这也是!)Lexical作用域与声明函数的位置有关,而动态作用域与从词法作用域调用函数的位置有关functionfoo(){console.log(a);}functionbar(){vara=3;foo();}vara=2;bar();//a=2动态作用域PS:假设JavaScript中有动态作用域,但没有functionfoo(){console.log(a);}functionbar(){vara=3;foo();}vara=2;bar();//a=3,可以想象动态作用域是激活的,全局调用我的bar,再次调用barfoo,调用foo打印console.log(a)。在这种情况下,可以理解为这样的思维模式:-functionfoo(){-console.log(a)-}functionbar(){vara=3;+functionfoo(){+console.log(a)+}}vara=2;bar()把foo函数移到bar函数里,然后我调用bar的时候,在bar函数里,一个自然就是3。这是区别于词法作用域的一种思维模式,和这个一样(谁打电话给我,我就指向他,很有动态感。)这也是JavaScript中比较着急的地方。一种语言中的两种语言这种模式的存在,当你学习作用域并使用作用域思维模式来理解this时,你常常会感到困惑,为什么this会来回引用,明明可以这样写this.name=name在子函数中,为什么还要先赋值给那个。现在看到动态范围并不令人困惑。原来作用域就是作用域,这就是作用域以动态的形式存在于对象中的作用。我这里有一个回调函数。当你理解了这个例子,你对作用域的理解就已经进入了大门:vara=1;console.log(a);varmyFunction=(function(){vara=2;console.log(a);varmyNextFunction=(function(){vara=3;console.log(a);varmyNextNextFunction=(function(){vara=4;console.log(a);})();})();})();每个函数中,都必须有一个作用域,作用域就是域。在我们的域中,打印的a是我范围内的a。作用域链就是如果我的作用域里没有作用域,我就从上级那里找,我就找这个变量(找不到就是undefined)。换个角度:函数中的变量都是私有变量,只有本函数可以访问。上层作用域不能访问下层作用域的变量作用域的分类在JavaScript中,作用域是代码执行的上下文。作用域有四种:全局作用域、函数作用域(也称为“局部作用域”)、块作用域(block)和eval作用域。函数内部由var定义的代码具有局部作用域。并且仅对该函数的其他表达式“可见”,包括嵌套/子函数中的代码。在全局作用域中定义的变量可以从任何地方访问,因为它是作用域链中最高的/最后的。以下代码,由于作用域的原因,foo的每个声明都是唯一的varfoo=0;//全局范围console.log(foo);//0varmyFunction=(function(){varfoo=1;//函数范围console.log(foo);//1varmyNestFunction=(function(){varfoo=2;//函数范围console.log(foo);//2})();})();eval('varfoo=3;console.log(foo);');//eval()作用域//let/const块作用域,变量不能被提升for(leti=0;i<5;i++){setTimeout(function(){console.log(i)});}因为全局作用域和函数作用域在作用域的介绍中已经提到,概念比较简单,这里不再赘述。eval()函数会将传入的字符串当做JavaScript代码来执行,也就是一个scope一句话,这个很容易理解,我就不多说了。让我们看看块级作用域。在ES6之前,我们没有块级作用域。ES6中的let关键字和const关键字可以构成块级作用域。让我们考虑一下什么时候没有块级作用域。会发生什么:for(vari=0;i<5;i++){setTimeout(function(){console.log(i);//55555});}我们希望它看起来像什么?在for循环中,每个i都是独立的,即使setTimeout有延时作用,每个i进入事件循环队列,然后一个一个打印出来,但实际情况是vari=0暴露了全局作用在域中,因为setTimeout有滞后性(只要是setTimeout,函数就会插入到宏任务中),所以先执行for循环,for循环的结果为1,2,3,4,5.但是因为i是一个全局变量,所以在setTimeout中如何统一为5呢?在ES6之前,for循环中的函数被改为立即执行函数(形成一个作用域),每执行一次循环,IIFE都会生成一个新的作用域,这样延迟函数的回调就可以圈出新的作用域在每个循环里面,每次迭代都会包含一个变量,它有正确的值供我们访问for(vari=0;i<5;i++){(function(j){setTimeout(function(){console.log(j);});})(i);}//12345每传入一个i就执行函数,每个i的作用域是独立的。后来有了ES6,就把var改成letfor(leti=0;i<5;i++){setTimeout(function(){console.log(i);});}道理很简单,因为let有自己的block-levelscope,详细作者在Promiseinterviewquestionthinkingextension中说明了情况,这里就不过多介绍了。只需要知道哪里有let和const,它定义的变量i被包裹在块级作用域中(域有绝对域,变量是自包含的),这样我们就有了另一种解决方法for循环问题中setTimeout参数的更改。在这里希望大家明白一个知识点。在ES6之前,JavaScript没有块级作用域的概念。只能通过立即执行函数来达到块级作用域的效果。改变。在ES6之后,let和const关键字可以起到块级作用域的作用。接下来说一下立即执行函数的含义。IIFE(immediateexecutionfunction)我们在开发网站的时候,经常会引入一些库,比如JQuery等,我们在使用这些库的时候,假设这些库写的很好,没有隐藏它们的内部作用域,那么我们就会面临命名冲突.为了解决这个问题,就有了模块化的概念。这个问题也在ES6中找到了答案。在importexport没有模块化之前,我们常用的方法是立即执行函数(IIFE)vara=1;(function(){vara=2;console.log(a);//2})()(function(name){console.log(name);//johnny})('johnny');//jquery(function(global,factory){})(typeofwindow!=="underfined"?window:this,function(窗口,非全局){});每个引用的库都被导入执行,变量存在作用域内,而库由于函数独立于库,所以命名方式互不影响,不会干扰全局。另一种理解:首先它是一个函数,所以它有一个作用域,作用域可以作为一个变量存在于函数中,不会暴露在全局上下文中,变量不会被污染;immediateexecution就是直接调用函数,这样你导入库的时候就可以直接使用了(一般库会挂载到window对象上)。匿名函数没有函数名,很容易理解。“工具人”varfoo=function(){console.log('helloworld');};foo();setTimeout(function(){console.log('hello,setTimeout');});立即执行函数直接调用就可以了,函数怎么调用,直接加上()(function(){console.log('helloworld');})();直接调用匿名函数,这种写法可以保证匿名函数中的变量是独立的。因为函数作用域内的变量不能被外界访问,所以变量是独立的。因此,立即执行的函数是一个函数,函数有(function)作用域,作用域内的变量不会受到外界的影响。作用域设计如下:functiondoSomething(a){b=a+doSomethingElse(a*2);控制台日志(b*3);}functiondoSomethingElse(a){返回a-1;}varb;doSomething(2);//15你觉得这种写法有问题吗?虽然我们可以这样写函数,但是因为作用域(词法作用域:定义在哪里,就形成了作用域),doSomethingElse的作用域是隐式全局构建的,也就是说doSomethingElse和doSomething是同一个Class作用域但这是我们的意思吗?不,我们希望doSomethingElse在doSomething函数中,它的作用域在doSomething作用域中functiondoSomething(a){functiondoSomethingElse(a){returna-1;}变量b;b=a+doSomethingElse(a*2);console.log(b);}doSomething(2);//15是为了私有化具体内容而设计的,变量b和函数doSomethingElse都属于doSomething的私有变量(或函数)。即全局作用域无法访问doSomethingElse,doSomethingElse只能在doSomething函数中调用。小结无论函数在何处被调用,如何调用,其词法作用域仅由函数声明的位置决定。作用域是指作用域由编写代码时声明函数的位置决定。编译的词法分析阶段基本上可以知道所有的标识符是在哪里声明的,是如何声明的,从而可以预测在执行过程中如何找到它们。词法范围是在词法阶段定义的范围。换句话说,词法作用域取决于您在编写代码时放置变量和块作用域的位置,因此当词法分析器处理代码时,它会保留作用域(它大部分时间都会这样做)作用域是在定义函数时确定的.该函数将保存一个[[scope]]属性,该属性保存父作用域对象的引用。什么构成深入理解JavaScript——万物皆对象深入理解JavaScript——Object(对象)深入理解JavaScript——whatnewdoes深入理解JavaScript——Object.create深入理解JavaScript——copy的秘密深入理解JavaScript——继承深入理解JavaScript——JavaScript中的第一位皇帝深入理解JavaScript——instanceof——寻祖深入理解JavaScript——函数深入理解ofJavaScript-scope深入理解JavaScript-this关键字深入理解JavaScript——Call、apply、bind将深入理解JavaScript——Immediatelyexecutedfunction(IIFE)深入理解JavaScript——深入理解JavaScript的词法环境——深入理解JavaScript的执行上下文和调用栈——深入理解ScopeVSExecutioncontext下JavaScript的立场——深入理解JavaScript的闭包——深入理解JavaScript的防抖节流——深入理解JavaScript的函数式编程——深入理解JavaScript的垃圾回收机制——深入理解JavaScript之数组——循环来这里深入理解JavaScript——字符串
