Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

作用域和闭包 #19

Open
miyuesc opened this issue Oct 22, 2019 · 0 comments
Open

作用域和闭包 #19

miyuesc opened this issue Oct 22, 2019 · 0 comments

Comments

@miyuesc
Copy link
Owner

miyuesc commented Oct 22, 2019

summary_start
...
summary_end

作用域

编译原理

传统语言的编译流程:
分词/词法分析(T/L) -- 解析/语法分析(Parsing) -- 代码生成
JavaScript引擎的编译过程要复杂的多,比如在语法分析和代码生成阶段有特定步骤来优化性能,包括对冗余元素进行优化。

理解作用域

人物:

  • 引擎:负责整个JavaScript程序的编译及执行过程。
  • 编译器:负责语法分析及代码生成等。
  • 作用域 负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

对话:比如执行var a = 2
该变量赋值的过程会执行两个动作:编译器在当前作用域中声明一个变量;运行时引擎在作用域中查找该变量,找到就对其赋值。

编译器

引擎的变量查询方式:LHSRHS
LHS:赋值操作左侧的查询,即查找变量容器的本身,已对其进行赋值;
RHS:retrieve his source value(取到他的源值),即查出变量当前的值。

引擎与作用域

function foo(a) { 
   console.log( a ); // 2 
} 
foo( 2 );

过程:
引擎:查询作用域,对foo进行RHS引用;
作用域:查找foo,返回给引擎;
引擎:执行foo,对参数a进行LHS引用;
作用域:查找a,返回给引擎;
引擎:对a进行赋值,继续执行,对console进行RHS引用,
作用域:查找console,返回给引擎;
引擎:继续执行,调用log函数,对a进行RHS引用;
作用域:查找a,返回
。。。

作用域嵌套

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到变量或者抵达最外层作用域(全局作用域)。

异常

如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError异常。
相比之下,如果引擎执行LHS查询时查询所有嵌套作用域没有找到所需变量,就会在顶层作用域(全局作用域)创建一个具有该名称的变量(前提是在非严格模式下);如果是严格模式,同样会抛出类似ReferenceError的异常。

词法作用域

词法阶段

简单来讲:词法作用域,即定义在词法阶段的作用域;换言之,就是由编辑代码将变量和块作用域写在哪里决定的。

function foo(a) { 
    var b = a * 2; 
 
    function bar(c) { 
        console.log( a, b, c ); 
    } 
 
    bar( b * 3 ); 
} 
 
foo( 2 ); // 2, 4, 12

这个例子中有三个逐级嵌套的作用域:

  1. 整个全局作用域,只有一个标识符foo;
  2. 包含foo所创建的作用域,有三个标识符a,bar,b
  3. 包含bar所创建的作用域,只有一个标识符c

作用域查找会在找到第一个匹配的标识符时停止

全局变量会自动成为全局对象(比如浏览器中的 window 对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引用来对其进行访问: window.a

欺骗词法

“修改”词法作用域,即欺骗此法作用域,JavaScript有两种机制来实现这个目的,但是欺骗词法作用域会导致性能下降

eval

eval()函数可以接受一个字符串作为参数,并将其内容作为编辑代码时就存在于这个位置的代码。

function foo(str, a) { 
    eval( str ); // 欺骗! 
    console.log( a, b ); 
} 
 
var b = 2; 
 
foo( "var b = 3;", 1 ); // 1, 3

eval(..) 调用中的 var b = 3; 这段代码会被当作本来就在那里一样来处理。
代码执行时,会找到foo内部的a和b,执行console.log()。

eval(..) 通常被用来执行动态创建的代码。

JavaScript中,还有setTimeout()setInterval()可以实现类似效果,但已经过时,不提倡使用。
new Function()函数的行为也很类似,最后一个参数可以接受代码字符串,这个构建函数语法比eval()略微安全,但是也不提倡使用。

with

JavaScript 中另一个难以掌握(并且现在也不推荐使用)的用来欺骗词法作用域的功能是with 关键字。
with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

var obj = { 
    a: 1, 
    b: 2, 
    c: 3 
}; 
 
// 单调乏味的重复 "obj" 
obj.a = 2; 
obj.b = 3; 
obj.c = 4; 
 
// 简单的快捷方式 
with (obj) { 
    a = 3; 
    b = 4; 
    c = 5; 
}

但是with有个副作用。

function foo(obj) { 
    with (obj) { 
        a = 2; 
    } 
} 
 
var o1 = { 
    a: 3 
}; 
 
var o2 = { 
    b: 3 
}; 
 
foo( o1 ); 
console.log( o1.a ); // 2 
 
foo( o2 ); 
console.log( o2.a ); // undefined 
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!

即实际上在 a = 2这一步创建了一个全局变量a。

eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。

严格模式下,with被完全禁止,eval()和with会被限制。

性能

eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。

JavaScript在编译阶段会进行数项的性能优化。有些优化只能依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数定义的位置,才能在执行过程中快速找到标识符。

函数作用域和块作用域

函数作用域

含义:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant