1.作用域Scope有两种主要的工作模型:第一种是大多数编程语言采用的最常见的词法作用域,另一种称为动态Scope;JavaScript采用的作用域模型是词法作用域。1.词法作用域词法作用域是指在编写代码时由函数声明的位置确定的作用域。编译的词法分析阶段基本上知道所有标识符在何处以及如何声明,因此可以预测在执行期间将如何查找它们。JavaScript中有两种机制来“欺骗”词法作用域:eval(..):可以计算一串包含一个或多个声明的“代码”,从而修改现有的词法作用域(在运行时);with:通过将对象引用视为作用域并将对象属性视为作用域中的标识符(也在运行时)来创建新的词法作用域。这两种机制的一个副作用是引擎无法在编译时优化范围查找,因为引擎只能谨慎地认为这种优化无效。使用这些机制中的任何一种都会导致您的代码运行速度变慢。2.函数作用域和块级作用域函数作用域:函数是JavaScript中最常见的作用域单元。从本质上讲,函数内部声明的变量或函数会“隐藏”在作用域中,即函数内部的函数和变量是函数私有的;块级作用域:块作用域是指变量和函数不仅可以属于它们所在的作用域,还可以属于一个代码块(通常指{..}里面)。在ES6之前,JavaScript中没有块级作用域(例外:try/catch结构在catch子句中有块作用域);let关键字(var关键字的表亲)在ES6中引入,用于在任意代码块中声明变量。如果(..){让a=2;}会声明一个劫持if{..}块的变量,并将变量添加到这个块中(另外,常量定义const也有块级作用域)。3、函数和变量的提升(1)、提升函数作用域和块作用域的行为是一样的,即某个作用域内的变量会附加到这个作用域。引擎在解释JavaScript代码之前对其进行编译。编译阶段的一部分是找到所有声明并将它们与适当的范围相关联;因此,在执行任何代码之前,首先处理包括变量和函数在内的所有声明;当看到vara=2时;,它可能被视为声明。但JavaScript实际上将其视为两个声明:vara;和一个=2;.第一个定义声明是在编译时进行的。第二个赋值语句保留在执行阶段。就好像变量和函数声明从它们在代码中出现的位置“移动”到顶部。这个过程叫做扬升。每个范围都会被提升;(2)、函数优先函数声明和变量声明将被提倡。但是首先提升函数,然后提升变量。富();//1varfoo;functionfoo(){console.log(1);}foo=function(){console.log(2);};将输出1而不是2!此代码片段将由引擎解释如下:functionfoo(){console.log(1);}foo();//1foo=function(){console.log(2);};varfoo尽管出现在functionfoo()...中,但它是重复声明(因此被忽略),因为函数声明在普通变量之前提升.尽管重复的var声明会被忽略,但出现在后面的函数声明可以覆盖前面的声明。2、作用域闭包(一)、理解闭包当一个函数能够记住并访问它所在的词法作用域时,就会产生一个闭包,即使该函数是在当前词法作用域之外执行的。在Javascript语言中,只有函数内部的子函数才能读取局部变量,所以闭包可以简单理解为“函数内部定义的函数”。本质上,闭包是连接函数内部和函数外部的桥梁。(2)闭包的目的是可以读取函数内部的变量;始终将变量的值保存在内存中。(3)生成的闭包实例可以读取函数内部的变量:functionfoo(){vara=2;functionbar(){console.log(a);}returnbar;}varbaz=foo();巴兹();//2-这就是闭包的作用。foo()执行后,通常预计foo()的整个内部作用域都会被销毁,因为我们知道引擎有一个垃圾收集器来释放不再使用的内存空间;关闭的“魔力”在于它可以防止这种情况发生。事实上内部作用域仍然存在,所以它没有被垃圾回收,因为bar()本身正在使用中;多亏了bar()的声明位置,它有一个闭包覆盖了foo()的内部作用域,使得该作用域可以保持活动状态以供bar()在此后的任何时间引用。bar()仍然持有对该范围的引用,该引用称为闭包。循环和闭包:for(vari=1;i<=5;i++){setTimeout(functiontimer(){console.log(i);},i*1000);}通常,我们使用这个的预期行为代码是分别输出1到5的数字,每秒一次,一次一个。但实际上这段代码在运行时会以每秒一次的频率输出五次6:延迟函数的回调要到循环结束才会执行。实际上,即使在定时器运行时每次迭代都执行setTimeout(..,0),循环结束后所有的回调函数仍然会被执行,所以每次都会输出一个6。实际情况是,循环中的5个函数虽然在每次迭代中都是分别定义的,但是它们都封闭在一个共享的全局作用域中,所以实际上只有一个i,即所有函数共享一个对i的引用。解决方案:使用IIFE,在每次迭代中将本次迭代的i传入createdscope,并关闭;对于(vari=1;i<=5;i++){(function(j){setTimeout(functiontimer(){console.log(j);},j*1000);})(i);}使用迭代中的IIFE为每次迭代创建一个新的作用域,允许延迟函数的回调到每次迭代中包含新的作用域,并且每次迭代将包含一个具有正确值的变量供我们访问。(4)使用闭包注意事项因为闭包会导致函数中的变量存放在内存中,内存消耗大,所以不能滥用闭包,否则会导致网页出现性能问题,可能导致内存泄漏.解决方法:在退出函数之前删除所有不用的局部变量。闭包会在父函数外改变父函数内变量的值。因此,如果将父函数作为对象,将闭包作为其公共方法(PublicMethod),将内部变量作为其私有属性(privatevalue),那么一定要注意不要改变a的值父函数中的变量。
