你不知道的JS之作用域和闭包(二)词法作用域
原文:你不知道的js系列
词法作用域(Lexical Scope)
Lex time
一个标准的编译器的第一个阶段就是分词(token化)
词法作用域就是在词法分析时定义的作用域。换句话说,词法作用域是你在写代码的时候,变量和代码块的位置决定的,因此在词法分析时也是固定不变的了。
注:有一些方法可以欺骗词法作用域,从而在词法分析结束之后,修改词法作用域。但好的实践是避免使用这些方法,让词法作用域只经过词法分析,也就是说完全保持编写时的作用域。
下面这段示例代码有三个嵌套作用域,
圈 1 包含了全局作用域,只有一个标识符 foo
圈 2 包含 foo 作用域,有三个标识符 a,bar,和 b
圈3 包含 bar 作用域,有一个标识符 c
作用域的范围是根据作用域代码块定义的位置决定的,在这里每个函数创建了一个作用域。
bar 的作用域完全包含在 foo 作用域中,因为 bar 是在 foo 中定义的
注:这里的作用域嵌套是严格的, 不存在像 Venn 图那样可以超出外部作用域的嵌套,一个函数不能同时存在于两个外部函数中。
查询
这些作用域的结构和相对位置就解释引擎需要找一个标识符时需要查询的位置。
如果在 bar() 和 foo() 的内部都存在变量 c,console.log() 就首先找到 bar 作用域中的变量,不会找到外部 foo 中的那个。
作用域查询一旦找到第一个匹配的标识符就会终止,对于相同的标识符名称,内部的标识符就会覆盖外部的标识符。不管如何覆盖,作用域查询总是从嵌套的最内层作用域开始查找。
注:全局变量就是全局对象的属性
所以当全局变量被覆盖时,可以通过全局对象进行引用(比如浏览器的 window 对象),被覆盖的非全局对象则无法被访问到了
无论一个函数在哪里被调用,或者如何被调用,它的词法作用域近在这个函数声明时定义。
词法作用域查询只查询第一级标识符,如 a,b 和 c,如果你有类似于 foo.bar.baz 这样的代码,词法作用域查询仅作用在标识符 foo ,一旦查找到这个变量,就会使用对象属性访问规则分别解析 bar 和 baz 属性。
欺骗词法作用域
虽然词法作用域仅在函数声明时定义,但是 JavaScript 有两种机制可以在运行时改变(欺骗)词法作用域,而这种行为会导致更低的性能。
eval
eval() 可以接收一个字符串作为参数,并把字符串的内容当作代码运行。也就是说,调用 eval() 的时候,相当于在你写好的代码里面生成代码,并且会运行这段代码,就好像是一开始就写好的一样,通过这种方式,eval() 就可以实现对词法作用域环境的修改。
在执行 eval() 之后的那些代码,引擎就无法知道也不去关心前面的代码是动态编译的,而且修改了词法作用域环境。引擎只会一如既往地进行词法作用域查询。
function foo(str, a) {
eval( str ); // cheating!
console.log( a, b );
} var b = 2; foo( "var b = 3;", 1 ); // 1 3
在 eval() 被调用的时候,字符串参数被当作真正的代码,这段代码声明了变量 b ,改变了 foo() 的词法作用域。在 foo 的内部创建了变量 b,覆盖了外部全局作用域中定义的变量 b,所以最后输出的结果是 1 3。
注:在这个例子中,我们传入的代码字符串是固定字面量,但是它可以很容易通过字符串拼接动态创建代码。eval() 通常用来执行动态创建的代码,因为字面量形式的静态代码和直接在程序中编写这样的代码相比,没什么好处。
如果这个字符串形式的代码包括多个声明语句,在 eval() 被调用的那个词法作用域就会被改变。技术上来说,eval() 可以间接地被调用,从而导致在全局作用域的上下文环境中执行,从而改变全局作用域。无论哪种情况,eval() 都可以在运行时改变代码编写时的词法作用域。
注:eval() 在严格模式下有它自己的词法作用域,在 eval() 内部的声明也就不会改变外部的作用域。
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
} foo( "var a = 2" );
JavaScript 还有其它工具和 eval() 有类似的效果。setTimeout() 和 setTnterval() 可以接收一个字符串作为第一个参数,这个字符串内容将会被 eval() 成一个动态生成的函数的内部代码。这是一种老旧的早已经被废弃的行为,不要这么做!
函数构造方法 new Function() 也可以在最后一个参数接收一个字符串,然后动态生成函数内部代码(前面的参数将作为新函数的参数),这种语法要比 eval() 安全一些,但也要避免使用。
with
在 JavaScript 中可以欺骗词法作用域的另一个特性就与 with 关键字,现在已经被废弃了。
with 通常被解释为一种访问对象属性的快捷方式,不用重复引用这个对象本身。
var obj = {
a: 1,
b: 2,
c: 3
}; // more "tedious" to repeat "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4; // "easier" short-hand
with (obj) {
a = 3;
b = 4;
c = 5;
}
但它不仅仅只是一个属性访问的快捷方式。
function foo(obj) {
with (obj) {
a = 2;
}
} var o1 = {
a: 3
}; var o2 = {
b: 3
}; foo( o1 );
console.log( o1.a ); // foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 -- Oops, leaked global!
在这个示例中, 在 with 代码块中,有一个对变量 a 的 LHS 引用,并且要赋值为 2。
然而当 foo 的参数为 o2 的时候,o2 并没有 a 这个属性,o2.a 就是 undefined。
但是副作用是,有一个全局变量 a 被创建并赋值为 2 了。
with 语句,将对应的对象看作一个独立的词法作用域,因此对象的属性被当作这个作用域里的标识符。
注:即使 with 代码块将对象看作一个词法作用域,在这个块中的 var 声明不会限制在这个块的作用域中,而是包含在外部函数作用域中。
所以在这里对 a 的 LHS 查询一直到全局作用域都没有完成,(非严格模式)就会自动创建一个全局变量。
注:with 语句在严格模式下禁止使用,eval() 则只保留核心功能。
性能
JavaScript 引擎在编译期间会进行各种优化,一些优化其实可以归结为在词法分析阶段,静态分析了代码,预先确定了变量和函数声明的位置,所以在执行期间就可以快速解析标识符。
但是如果引擎在代码中找到一个 eval() 或 with 语句,那么它会假设它知道的标识符位置可能是无效的,因为在词法分析期间它不知道到底给 eval() 传入了什么参数,或者 with 绑定的对象创建的词法作用域的内容。
如果 eval 或者 with 存在,那么大部分优化都将毫无意义,所有引擎干脆不做优化了。没有优化,代码运行起来肯定就非常慢了。
Don't use them.
你不知道的JS之作用域和闭包(二)词法作用域的更多相关文章
- JavaScript闭包之“词法作用域”
大家应该写过下面类似的代码吧,其实这里我想要表达的是有时候一个方法定义的地方和使用的地方会相隔十万八千里,那方法执行时,它能访问哪些变量,不能访问哪些变量,这个怎么判断呢?这个就是我们这次需要分析的问 ...
- 深入理解javascript作用域系列第二篇——词法作用域和动态作用域
× 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...
- 你不知道的Javascript(上卷)读书笔记之二 ---- 词法作用域
在前一篇文章中,我们把作用域定义为"管理.维护变量的一套规则",接下来是时候来深入讨论一下Js的作用域问题了,首先我们要知道作用域一般有两种主要的工作类型,一种是词法作用域,一种是 ...
- 你不知道的JS系列【1】- 什么是作用域
几乎所有的编程语言都能够储存变量,并且能在之后对这个变量值进行访问或修改,正是储存和访问变量的能力将状态带给了程序,那么,这些变量储存在哪里呢?程序需要时又是如何找到他们?这些问题说明需要一套设计 ...
- 《你不知道的JavaScript(上)》笔记——词法作用域
词法作用域是一套关于引擎如何寻找变量以及会在何处找到变量的规则. 词法作用域最重要的特征是它的定义过程发生在代码的书写阶段(假设你没有使用eval() 或 with) 欺骗词法:指修改词法作用域, 欺 ...
- 前端知识体系:JavaScript基础-作用域和闭包-JavaScript的作用域和作用域链
JavaScript的作用域和作用域链 作用域: 变量的作用域无非两种:全局作用域和局部作用域 全局作用域: 最外层函数定义的变量拥有全局作用域.即对任何内部函数来说都是可以访问的. <scri ...
- 你不知道的JS之作用域和闭包(三)函数 vs. 块级作用域
原文:你不知道的js系列 在第(二)节中提到的,标识符在作用域中声明,这些作用域就像是一个容器,一个嵌套一个,这个嵌套关系是在代码编写时定义的. 那么到底是什么产生了一个新的作用域,只有函数能做到 ...
- JS教程:词法作用域和闭包 (网络资源)
varclassA = function(){ ; } classA.prototype.func1 = function(){ var that = this, ; function a(){ re ...
- javasrcipt的作用域和闭包(二)
这篇博客主要对词法作用域与欺骗词法作用域.函数作用域与块级作用域.函数内部的变量提成原理进行详细的分析,在这篇博客之前,关于作用域.编译原理.浏览器引擎的原理及关系在javaScript的作用域和闭包 ...
随机推荐
- python笔记01-05
作者:Vamei 出处:http://www.cnblogs.com/vamei https://blog.csdn.net/flyfrommath/article/details/77447587 ...
- JUC--volatiley&CAS
public class VolatileTest { public static void main(String[] args) { ThreadDemo td = new ThreadDemo( ...
- Toad DBA Suite for Oracle 12.6 64-bit Commercial 简单连接
注意:Toad DBA Suite for Oracle 12.6 64-bit Commercial安装包推荐去官网下载,中文版的最好不要使用绿色免安装版,不然连接会报各种错误 1.安装:双击下载好 ...
- ubuntu系统的teamviewer的安装及使用
参考链接: 安装: https://blog.csdn.net/weixin_34613450/article/details/80541799 使用: https://jingyan.baidu.c ...
- A - Alice's Print Service ZOJ - 3726 (二分)
Alice is providing print service, while the pricing doesn't seem to be reasonable, so people using h ...
- Python turtle模块小黄人程序
讲解Python初级课程的turtle模块,简单粗暴的编写了小黄人的程序.程序还需要进一步优化.难点就是要搞清楚turtle在绘制图形过程中的方向变化. import turtle t = turtl ...
- jsp中一个标签两种方式绑定两个click事件导致未执行的问题
近日,在开发过程中,写了一个标签 <li id="a1" onclick="doSomething()">...</li> 在js页面中 ...
- 处理soapUI特殊返回报文 【原】
String message ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + & ...
- adb连接夜神模拟器执行命令
1.要进入夜神模拟器的bin目录 2.连接夜神模拟器 3.执行命令 cd %~dp0 nox_adb.exe connect 127.0.0.1>nul set num= :ok set /a ...
- react安装 项目构建
1.nodejs安装 下载安装包,解压.如果是已编译文件,在/etc/profile中设置PATH(/etc/profile文件中的变量设置,所有用户可用,但需求重启服务器),并source /etc ...