简单了解下js的作用域

104 阅读5分钟

js的作用域

js的编译过程分为四个步骤,分别是词法分析、语法分析、代码生成和执行

一、词法分析(Lexical Analysis)

词法分析是编译过程的第一步,也称为扫描(Scanning)。在这一阶段,编译器将源代码的字符流转换为词法单元(Token)流。词法单元是源代码中的基本语法元素,如关键字、标识符、字面量、运算符和分隔符等。

对于 var a = 1,词法分析的结果可能是:

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

这些词法单元将被传递给下一阶段的语法分析器。

二、语法分析(Syntax Analysis)

语法分析是编译过程的第二步,也称为解析(Parsing)。在这一阶段,编译器根据语言的语法规则将词法单元流转换为一个树状的数据结构,称为抽象语法树(AST, Abstract Syntax Tree)。AST是源代码的抽象表示,它反映了源代码的语法结构。

对于 var a = 1,语法分析的结果可能是一个简单的AST,其中包含以下节点:

  • 变量声明节点(声明了变量 a
  • 赋值操作节点(将值 1 赋给变量 a

三、代码生成(Code Generation)

代码生成是编译过程的最后一步。在这一阶段,编译器将AST转换为可执行的机器代码。这个过程可能涉及多个中间步骤,如中间代码生成、优化和目标代码生成等。

对于 var a = 1,代码生成的结果可能是一条或多条机器指令,这些指令在计算机上执行时将实现以下操作:

  1. 在内存中分配一个空间用于存储变量 a
  2. 将数值 1 存储到变量 a 所占用的内存中。

作用域的基本规则

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

var a = 1

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

image.png

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

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

image.png 报错:a在全局作用域中没有被定义

a在内层函数作用域foo()中被定义,不能被外层作用域使用


作用域的bug,欺骗语法

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

eval()能将字符串转化成代码

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

image.png eval() 执行的代码在调用它的作用域中运行,这意味着它可以访问和修改该作用域内的变量和函数。这可能导致意外的变量覆盖或数据泄露。

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

一般修改对象属性的方法是

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

这种方法不会导致属性的泄露,不能在全局访问这种方法修改的属性,但可以用obj.a的方法访问

少见的函数with(){}

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

with (obj) {
  a = 2
  d = 4
}
console.log(obj);
console.log(d)

被with(){}添加的属性d,从对象中泄露到了全局中,能在全局作用域中被使用


var let const

var,let,const都是定义变量的关键字。

var和let的区别

let是在js的ES6版本中引入的,与var相比引入块级作用域,避免出现变量提升导致的一些存在问题

1.声明提升 var 声明的变量会存在声明提升(将变量的声明提升到当前作用域的顶部)let 不会

console.log(a);
var a=1

image.png var声明的变量能够被调用,但还没有定义,所以显示undefined

var会把声明变量的时间提前 类似于

var a
console.log(a)
var a=1

2.var 可以重复声明变量,let 不行

var a=1
var a=2

image.png

let a=1
let a=2

image.png

3.var 在全局声明的变量会默认添加在 woindow 对象上,let 不会

let和const的区别

const 声明的变量值无法修改,但const声明的对象的属性是可以改变的,因为const生成一个对象,const保存的是一个指向一个对象的地址,改变属性不会改变对象的地址。


块级作用域

构成:let + {}

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

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

image.png

暂时性死区

let a = 1

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

image.png

  1. let a = 1;:在全局作用域中声明了一个变量a并初始化为1。
  2. if (true) { ... }:这个条件判断总是为真,因此内部的代码块会执行。
  3. console.log(a);:在这一行,由于它位于let a = 2;之前,但又在同一个作用域(这里的if块作用域,但注意let在块内重新声明会创建一个新的局部变量,而不是修改外部的a)导致不会访问全局作用域的a,但块级作用域的let a=2不会在块级定义域中提前声明,所以不能找到a
  4. let a = 2;:在if块内部,声明了一个新的局部变量a并初始化为2。这个a只在这个if块内部可见和有效。