You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function(str,a){eval(str);// eval()调用"var b = 3;",这段代码会被当作就在那里一样来处理// 由于eval()调用的代码声明了一个新的变量b,因此它对已经存在的foo()的词法作用域进行了修改(也就是说,这段代码实际上在foo()内部创建了一个变量b,并且遮蔽了外部(全局变量)作用域中的同名变量)console.log(a,b);}varb=2;foo("var b = 3;",1);// 1,3
词法作用域/函数作用域/块级作用域
词法作用域
作用域查找会在找到第一个匹配的标识符时停止。在多层嵌套作用域中可以定义同名的标识符——“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应,作用域的查找始终从运行时所处的最内部作用域开始,逐级向外或者说是向上进行,直到遇见第一个匹配标识符为止。
无论函数在哪里被调用,无论函数如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
词法作用域查找只会查找一级标识符,比如a、b、c,如果代码中引用了foo.bar.baz,词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则会分别接管对bar和baz属性的访问。
欺骗词法作用域(修改词法作用域)——在运行期修改书写期的词法作用域
eval
javascript中的eval()函数可以接收一个字符串作为参数,并将其中的内容视为好像在书写时就存在于程序中的这个位置。也就是说,可以在我们写的代码中用程序生成代码并运行,就好像代码是写在那个位置一样。
默认情况下,如果eval()中所执行的代码包含有一个或多个声明(无论是变量还是函数),都会对eval()所处的词法作用域进行修改。
在严格模式中,eval()在运行时有其自己的词法作用域,也就是说其中的声明无法修改所在的作用域。
with
with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
with会将对象及其属性放进一个作用域并同时分配标识符。
性能
javascript引擎会在编译阶段进行数项性能优化,其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。
但是如果引擎在代码中发现了eval()/with,它只能简单地假设关于标识符位置的判断都是无效的,因为无法再词法分析阶段明确指定eval()会接收到什么代码,这些代码如何对作用域进行修改,也无法知道传递给with用来创建新词法作用域的对象内容到底是什么。
如果出现了eval()/with,很可能所有的优化都是无意义的。
函数作用域
函数作用域是指:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上,在嵌套的作用域中也可以使用)。
定义一个函数
隐藏内部实现
从所写的代码中挑选一个任意的片段,然后用函数声明来对其进行包装,实际上就是把这些代码给“隐藏”起来。
实际上就是在这个代码片段的周围创建一个作用域气泡,也就是说这段代码中的任何声明(变量或函数)都将绑定在这个新创建的包装函数的作用域中,而不是先前所在的作用域中。所以说,可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。
规避冲突
“隐藏”作用域中的变量和函数可以避免同名标识符之间的冲突,两个同名标识符可能用途不一样,无意间会造成冲突,并且可能发生变量的值被覆盖。
解决方法:
函数作用域
在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何东西。
但是这样会增加额外的问题:首先,必须要声明一个具名函数foo(),这样foo()本身就“污染”了所在作用域;其次,必须显示地通过函数名(foo())调用这个函数才能运行其中的代码。
改进:
(function foo(){...})()会被看成函数表达式而不是一个标准的函数声明来处理。
foo变量名被隐藏在自身的函数中而不是在所在作用域中。
匿名函数
匿名函数表达式,比较典型的就是回调函数:
匿名函数表达式书写起来简洁快捷,但是存在几个缺点:
使用行内函数表达式能够解决这个问题,始终给函数表达式命名是一个最佳实践:
立即执行函数表达式
IIFE:立即执行函数表达式:
函数被包含在一对()括号内部,因此成为了一个表达式,通过在末尾添加另一个()可以立即执行这个函数。
IFEE有一个非常普遍的进阶用法——把它们当作函数调用并传递参数进去:
IFEE还有一个用途就是倒置代码的运行顺序:将需要运行的函数放在第二位,在IFEE执行之后当作参数传递进去:
块级作用域
块作用域是指变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常是指{...}内部)。
块级作用域是一个队最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏信息。
更多关于块级作用域的解说,可以点击 这里
提升
包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理——变量和函数声明从它们的代码中出现的位置被“移动”到了最上面,这过程叫做“提升”。
函数优先
函数声明和变量都会被提升,但需要注意的是,函数会首先被提升,然后才是变量。
The text was updated successfully, but these errors were encountered: