前端web开发使用js的基础教程 全程干货(第一章作用域)

**

什么是js

**作为一个语言来说,js总是成为大家批评的对象,部分原因是它有很多历史遗留问 题,,JavaScript 听上去都给人一种“蠢蠢的感觉,就像是它更成熟的大哥 Java 的不完整版本。
不过这两个语言有许多本质上的区别。。 JavaScript 借鉴了许多语言的概念和语法,比如 C 风格的过程式编程以及不太明显的 Scheme/List 风格的函数式编程,因此吸引了许多开发者,甚至是那些不会编程的新手。用 JavaScript 来编写“Hello World”是非常简单的,因此这门语言很有吸引力并且很好上手。 虽然 JavaScript 可能是最早出现的语言之一,但是由于其本身的特殊性,相比其他语言,能 真正掌握 JavaScript 的人比较少。如果想用 C、C++ 这样的语言编写功能全面的程序,那需 要对语言有很深的了解。但是对于 JavaScript 来说,编写功能全面的程序仅仅是冰山一角。 JavaScript 语言本质上有许多复杂的概念,但是却用一种看起来比较简单的方式体现出来, 比如回调函数,
JavaScript 开发者通常只是简单地使用这些特性,并不会关心语言内 部的实现原理。 JavaScript 既是一门充满吸引力、简单易用的语言,又是一门具有许多复杂微妙技术的语 言,即使是经验丰富的 JavaScript 开发者,如果没有认真学习的话也无法真正理解它们。

第一部分

什么是作用域以及LHS与RHS的区别

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

  2. 什么是作用域
    作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对 变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。

  3. 什么是LHS和RHS法则
    不难猜出L”和“R”的含义,它们分别代表左侧和右侧,
    换句话说,当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。

让我们结合实例来讲
console.log( a );
其中对 a 的引用是一个 RHS 引用,因为这里 a 并没有赋予任何值。相应地,需要查找并取 得 a 的值,这样才能将值传递给 console.log(…)。

在如a = 2;
这里对 a 的引用则是 LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为 = 2 这个赋值操作找到一个目标

而如 function foo(a)
{
console.log( a ); // 2
}
foo( 2 );
在这里 最后一行 foo(…) 函数的调用需要对 foo 进行 RHS 引用,意味着“去找到 foo 的值,并把 它给我”。并且 (…) 意味着 foo 的值需要被执行,
这里还有一个容易被忽略却非常重要的细节。
代码中隐式的 a=2 操作可能很容易被你忽略掉。这个操作发生在 2 被当作参数传递给 foo(…) 函数时,2 会被分配给参数 a。为了给参数 a(隐式地)分配值,需要进行一次 LHS 查询。
这里还有对 a 进行的 RHS 引用,并且将得到的值传给了 console.log(…)。本身也需要一个引用才能执行,因此会对 console 对象进行 RHS 查询,并且检查得到的值中是否有一个叫作 log 的方法。

词法作用域

  1. 词法阶段
    简单地说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。

  2. 作用域的判别
    考虑以下代码:

考虑以下代码:
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。

作用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息 来查找标识符的位置。 在上一个代码片段中,引擎执行 console.log(…) 声明,并查找 a、b 和 c 三个变量的引 用。
首先从最内部的作用域,也就是 bar(…) 函数的作用域气泡开始查找。引擎无法在 这里找到 a,因此会去上一级到所嵌套的 foo(…) 的作用域中继续查找。在这里找到了 a, 因此引擎使用了这个引用。对 b 来讲也是一样的。而对 c 来说,引擎在 bar(…) 中就找到 了它。 如果 a、c 都存在于 bar(…) 和 foo(…) 的内部,console.log(…) 就可以直接使用 bar(…) 中的变量,而无需到外面的 foo(…) 中查找。 作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的 标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应, 作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见 第一个匹配的标识符为止。
这也就是作用域的查找过程。

特殊
什么是欺骗词法
如果词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修 改”(也可以说欺骗)词法作用域呢? JavaScript 中有两种机制来实现这个目的。社区普遍认为在代码中使用这两种机制并不是 什么好注意。但是关于它们的争论通常会忽略掉最重要的点:欺骗词法作用域会导致性能 下降。 在详细解释性能问题之前,先来看看这两种机制分别是什么原理。

2.2.1 eval JavaScript
eval(…) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书 写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并 运行,就好像代码是写在那个位置的一样。 根据这个原理来理解 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;” 这段代码会被当作本来就在那里一样来处理。由于那段代 码声明了一个新的变量 b,因此它对已经存在的 foo(…) 的词法作用域进行了修改。事实 上,和前面提到的原理一样,这段代码实际上在 foo(…) 内部创建了一个变量 b,并遮蔽 了外部(全局)作用域中的同名变量。 当 console.log(…) 被执行时,会在 foo(…) 的内部同时找到 a 和 b,但是永远也无法找到 外部的 b。因此会输出“1, 3”而不是正常情况下会输出的“1, 2”。

默认情况下,如果 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(…) 和 setInterval(…) 的第一个参数可以是字符串,字符串的内容可以被解释为一段动态生成的 函数代码。这些功能已经过时且并不被提倡。不要使用它们! new Function(…) 函数的行为也很类似,最后一个参数可以接受代码字符串,并将其转 化为动态生成的函数(前面的参数是这个新生成的函数的形参)。这种构建函数的语法比 eval(…) 略微安全一些,但也要尽量避免使用。 在程序中动态生成代码的使用场景非常罕见,因为它所带来的好处无法抵消性能上的损失。

本章小结

词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段 基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它 们进行查找。 JavaScript 中有两个机制可以“欺骗”词法作用域:eval(…) 和 with。前者可以对一段包 含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在 运行时)。后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作 用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。 这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认 为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。所以最好不要使用它们。

~~尾声截至

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值