简要叙述js的编译原理和作用域

138 阅读4分钟

编译原理

js的执行引擎是有浏览器和node来执行 js的编译过程分为四个步骤:分别是词法分析,语法分析,代码生成和执行

一.词法分析(Lexical Analysis)

js的编译过程的第一步,也称为扫描。在此阶段编译器会先扫描全部代码将代码的字符串转换成词法单元流,如关键字,标识符,字面量,字符串等

例如定义变量 var a = 1,词法分析会识别为如下:

  • 关键字 var
  • 标识符 a
  • 赋值运算符 =
  • 数值字面量 1

这些词法将传递给下阶段的词法分析器

二.语法分析(Syntax Analysis)

它是根据词法分析得到的此法单元序列,然后对这些词发的单元流进行解析,然后这些词法单元转换成抽象语法树,语法树是程序语法结构的直观表示,后续代码生成和优化等阶段会基于语法树进行

三.生成代码

编译器或解释器会根据前面阶段构建语法树和符号等信息,生成目标机器或者虚拟机能够执行的代码。例如上述 var a = 2 就会被转换成一个机械指令,创建一个变量 a 将值 2 存储到 a 当中

作用域

js有全局作用域和函数作用域,作用域法则:是内层作用域可以访问外层作用域但外层作用域不能访问内层作用域

词法作用域

词法作用域是有你在写代码时将变量和块级作用域写在哪里来决定,如下示例所示:

var a=1

function foo(b){
    console.log(a + b)
}
foo(3)

外层全局作用域中的变量a和foo()能够被内层函数作用域访问

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

屏幕截图 2024-11-17 173210.png 会出现如上图片展示报错:a在全局作用域中没有被定义 原因如下:a在内层函数作用域foo()中被定义,不能被外层作用域使用

作用域中的欺骗语法

欺骗语法有两个,一个是 eval() 和 with(){} ,他们是打破了作用域规则的两个函数。

eval()能将字符串转换成代码
function foo(str,a){
    eval(str);
    console.log(a,b);
}
var b = 2;
foo('var b =3', 1);

该代码运行结果为3,1 在次段代码中字符串var b = 3,被eval()转换成了代码var b = 3,将 3 这一值赋予给定义的变量b eval()执行的代码在调试它的作用域中运行,这意味着它可以访问和修改作用域的变量和函数。这可能会导致意外的变量覆盖或数据泄露

with(){}是一个修改对象属性的函数

一般修改对象的方式如下代码所示

var obj={
    a:1,
    b:2,
    c:3
}
obj.a=2
obj.b=3
obj.c=4

这是我们平常通用对对象内容更改的方式而当我们使用with(){}方式时会出现以下结果

var obj={
    a:1,
    b:2,
    c:3

}

// obj.a=2
// obj.b=3
// obj.c=4
with(obj){
    a=3
    b=4
    c=5
    d=4
}

在此段代码运行中a,b,c的只是正常改变了的,而在的变量d并赋予的值为4此段新赋值会被泄露到全局中,能在全局作用域中被使用

var let const 区分

var,let,const 这三个都是定义变量的关键字

var 和 let 区别

在es6之后引入了新的定义变量的关键字let,这是避免在es6之前存在的var定义变量会出现变量声明提升的整个一问题,var 和 let 之间区别如下:

  1. var 声明的变量会存在声明提升(将变量的声明提升到当前作用域的顶部),let 声明不会提升
  2. var 可以重复声明变量let 不行
  3. var 在全局声明的变量会默认添加在window对象上,而let不会
  4. var 可以声明多个重复变量后者会覆盖前者
let和const的区别

let和const之间主要的区别就是const定义的变量之无法改变是一个常量。

块级作用域

构成就是let+{}

let声明的变量只在块级作用域中生效

for(let i =0 ;i<10;i++)
{
    console.log(i);
    
}

屏幕截图 2024-11-17 191501.png

暂时性死区
let a=1
if(true){
    console.log(a);//暂时性死区
    let a=2
    
}

该代码在运行时会出现报错现象原因如下:

  • let a = 1;:在全局作用域中声明了一个变量a并初始化为1。

  • if (true) { ... }:这个条件判断总是为真,因此内部的代码块会执行。

  • console.log(a);:在这一行,由于它位于let a = 2;之前,但又在同一个作用域(这里的if块作用域,但注意let在块内重新声明会创建一个新的局部变量,而不是修改外部的a)导致不会访问全局作用域的a,但块级作用域的let a=2不会在块级定义域中提前声明,所以不能找到a

  • let a = 2;:在if块内部,声明了一个新的局部变量a并初始化为2。这个a只在这个if块内部可见和有效。