你不知道的javascript上卷pdf 你不知道的javascript github_你不知道的javascript


《你不知道的JavaScript》上卷 的精华整理

本篇讲述:【JavaScript编译】【函数作用域】【块作用域】

第一部分 作用域和闭包

第一章 作用域是什么

1.1 编译原理

  • 分词/词法分析
    例:var a = 2;对这段代码进行词法分析,通常会分解成var a = 2 ;
  • 解析/语法分析
    生成“抽象语法树(Abstract Syntax Tree, AST)”
  • 代码生成
    通过AST编译成一组机器指令

注:JavaScript引擎的编译要比以上复杂的多。例如:在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化。

1.2 理解作用域

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

接下来看一段代码执行过程中他们是如何进行交流
代码:


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

foo( 2 );


对话:

“引擎:我说作用域,我需要为foo进行RHS引用。你见过它吗? 作用域:别说,我还真见过,编译器那小子刚刚声明了它。它是一个函数,给你。 引擎:哥们太够意思了!好吧,我来执行一下foo。 引擎:作用域,还有个事儿。我需要为a进行LHS引用,这个你见过吗? 作用域:这个也见过,编译器最近把它声名为foo的一个形式参数了,拿去吧。 引擎:大恩不言谢,你总是这么棒。现在我要把2赋值给a。 引擎:哥们,不好意思又来打扰你。我要为console进行RHS引用,你见过它吗? 作用域:咱俩谁跟谁啊,再说我就是干这个的。这个我也有,console是个内置对象。给你。 引擎:么么哒。我得看看这里面是不是有log(..)。太好了,找到了,是一个函数。 引擎:哥们,能帮我再找一下对a的RHS引用吗?虽然[…]” 摘录来自: Kyle Simpson、赵望野、梁杰. “你不知道的JavaScript(上卷)”。 iBooks.

1.3 作用域嵌套

在当前作用域中无法找到某个变量时,引擎就会在外层嵌套作用域中继续查找,知道找到该变量,或抵达最外层的全局作用域为止。

1.4 异常

如果在全局作用域中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下

如果ES5中引入了“严格模式”,那么严格模式会禁止自动的或隐式的创建全局变量。因此在查询失败的时候会抛出类似ReferenceError异常。

如果你对一个已查询到的变量进行不合理的操作时,比如对一个非函数类型的值进行函数调用,或者引用null undefined类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫做TypeError

1.5 小结

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。

var a = 2

首先,var a在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。接下来,a = 2会查询(LHS查询)变量a并对其进行赋值。 摘录来自: Kyle Simpson、赵望野、梁杰. “你不知道的JavaScript(上卷)”。 iBooks.

第二章 词法作用域

2.2 词法阶段

气泡1包含着整个全局作用域,其中只有一个标识符:foo
气泡2包含着foo所创建的作用域,其中有三个标识符:s bar b
气泡3包含着bar所创建的作用域,其中只有一个标识符:c

作用域查找会在找到第一个匹配的标识符时停止,在多层的嵌套作用域中可以定义同名的标识符,这叫做“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)

全局变量会自动成为全局对象(比如浏览器中的window对象)的属性。


var a = 1;

function fun1() {
  var a = 2;
  console.log(a)//输出2
}

function fun2() {
  var a = 2;
  console.log(window.a)//输出1
}


对浏览器中window全局对象的解释可以进一步到以下网址了解:

  • JavaScript全局对象——W3C
  • JavaScript声明全局变量三种方式的异同

2.2 欺骗词法

eval with 等语法会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。而且会导致JavaScript引擎在编译阶段无法对它们做任何的优化处理。
简单介绍一下这两个语法:

  • eval可以对一段包含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在运行时)。
  • with本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)

这两个机制的副作用会导致代码运行变慢。不要使用它们。

第三章 函数作用域和块作用域

  • 每一个函数都会创建一个自己的作用域,如果你在该作用域中声明了变量,且该变量存在于外部作用域,那么它会遮蔽外部作用域中的变量。
  • 为了不污染全局作用域,可以将代码写入一个匿名函数中调用
var a = 2;

(function() {
  var a = 3;
  console.log( a ); // 3
})();

console.log( a ); // 2


  • 变量声明应该距离使用的地方越近越好,并最大限度的本地化
  • let
{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1


  • const
var foo = true;

if (foo) {
  var a = 2;
  const b = 3; // 包含在if中的块作用域常量
  a = 3; // 正常!
  b = 4; // 错误!
}

console.log( a ); // 3
console.log( b ); // ReferenceError!



博客:


Rainey's Blograiney.space

你不知道的javascript上卷pdf 你不知道的javascript github_标识符_02