javascript 01 作用域

javascript-01 作用域

在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。

编译原理

分词/词法分析(Tokenizing/Lexing)

分词(tokenizing)和词法分析(Lexing)之间的区别是非常微妙、晦涩的, 主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。简 单来说,如果词法单元生成器在判断 a 是一个独立的词法单元还是其他词法 单元的一部分时,调用的是有状态的解析规则,那么这个过程就被称为词法 分析

例如:

var a = 12;

这段程序通常会被分解成 为下面这些词法单元:var、a、=、12 、;。 ##解析/语法分析(Parsing)
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法 结构的树。这个树被称为“抽象语法树” -- 简称 AST
AST: AST 抽象语法树

代码生成

将 AST 转换为可执行代码的过程称被称为代码生成

指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。

理解编译原理过程

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

理解程序的执行过程

var a = 12;

如果这是面试题,面试官问我解析这个语句的过程。
我会说,为一个变量 a 分配一个内存,然后将 12 的值,放到这个变量里面。显然没有毛病,但是面试官可能想听的并不是这个,假如编译器是面试官的话,以上的过程就并不完全正确

var一个变量的时候a,编译器会问作用域,这个变量名称是否存在于作用域中,如果为是的话,编译器会忽略,继续编译,但如果为否的话,编译器会要求作用域,在当前的作用域中声明一个变量命名为 a。
赋值过程同样也是相同的道理 a = 12
引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的 变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量
如果引擎最终找到了 a 变量,就会将 12 赋值给它。否则引擎就会举手示意并抛出一个异常!
---2020 年 12 月 总结--
参考地址:
你不知道的 JavaScript
https://segmentfault.com/a/1190000016231512
https://blog.csdn.net/zhixingheyi_tian/article/details/80040003

词法作用域

简析作用域

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

有时候会有类似的面试题,当看到关于简析题时候,要先分析下我们的代码中变量,以及作用域。先剥洋葱,要看我们的代码的切套关系。

  • 01 最外层是我们的全局作用域,能看到只有foo()
  • 02 中心层我们能看到的标识有 作为参数的a 被定义的bbar
  • 03 内心层可以看到的标识只有 作为参数的c

现在我们把代码放到运行环境中,看我们的代码是如何执行的

浏览器引擎看到 console.log(a, b, c);的声明。并开始找所打印的 a,b,c,变量。

  • a 首先我们在内心层找 a,也就是 bar()中,如果有就进行对应操作或运算,如果没有就去上一层找 ,在中心层找到了我们的a,我们的 a 是作为外层foo()的一个参数,所有 a 就是我们传参的 2

  • b 同理 在中心层找到我们 b, var b = a * 5; 而 a 已经是我们传参的 a = 2 了,所以 b 就等于 10

  • c 同理

全局作用域

var outerVar = "outer";
function fn() {
  console.log(outerVar);
}
fn(); //outer

最外层函数定义的变量拥有全局作用域,对于内部的函数来说,是可以访问的

局部作用域

function fn() {
  var innerVar = "inner";
  console.log(innerVar); //inner
}
fn();
console.log(innerVar); // ReferenceError: innerVar is not defined

函数作用域

由上面的的代码简析过程,我们得知,每个函数都会生成一层洋葱层.

function foo(a) {
  var b = 2; // 一些代码
  function bar() {
    // ...
  }
  // 更多的代码
  var c = 3;
}
bar(); // ReferenceError: bar is not defined
console.log(a, b, c); //ReferenceError: a,b,c is not defined

我们在全局无法访问函数内部的所定义的变量,但是在函数内部却可以访问,foo(..) 的内部都是可以被访问的,同样在 bar(..) 内部也可以被访问,【前提是 bar()中没有与 foo()同名的变量】

function foo(a) {
  var b = 2; // 一些代码
  console.log(a);
  function bar() {
    // ...
  }
  // 更多的代码
  var c = 3;
  console.log(a, b, c); //undefined 2 3
}
foo();

因为 a 是参数,我们并未传参,所以是 undefined。

就是因为这个特性,我们可以将一些私有函数,和对象,放到函数内部,从最小特权原则,

function doSomething(a) {
  b = a + doSomethingElse(a * 2);
  console.log(b * 3);
}
function doSomethingElse(a) {
  return a - 1;
}
var b;
doSomething(2); // 15

---待续

posted @ 2020-12-30 09:46  张丑丑呀  阅读(142)  评论(0编辑  收藏  举报