在众多编程语言当中都会使用到变量这一技术(变量使用来存储数据的容器,管理程序状态的),但是变量如何存储在内存之中以及如何查找并使用它成为了一个问题.而作用域的目的恰好是告诉我们以什么样的规则来查找使用变量.
在每一个函数中,每一个代码块中(使用let const 方式声明变量),全局中,都会形成一个作用域
//假设js是动态作用域的话那么输出的就是1 实际上的输出值是2 因为js中使用的是词法作用域
function foo(){
console.log(a)
}
function bar(){
let a =1
foo()
}
bar()
let a =2
查找变量是左查询和右查询(retrieve his source value),那么什么是左查询呢?左查询是寻找到变量这个容器本身,给变量进行赋值操作的,右查询是找到这个变量拿它的值的.函数声明是不是也是这样?function a(){}
,既然我都问了,那么就不是咯,函数声明是在编译阶段,声明和值就被同时定义了.
let a = 'zy' //左查询
console.log(a) //右查询
我们的作用域常常会进行层层嵌套的,js中能生成作用域的场景有函数,代码块(let const),全局作用域,
在作用域中内层的作用域是可以访问外层作用域中的变量的,假设我们的函数里面引用了一个变量,但是函数内部并未声明这个变量,那么它会向上层查找直至到全局作用域,如果全局作用域中也没有那么就会返回一个?
function foo(){
//如果变量中未声明a的话 那么在非严格模式下 全局作用域中会产生一个 var a,严格模式下TypeError
console.log(a)
}
tips:如果内层作用域中和外层声明了同一个变量,那么内层会遮蔽掉外层的,这就是人们常说的’遮蔽效应’(我没说过TUT)
function(){
let a = 1
function(){
console.log(a)
}
}
程序的编译原理
1.词法化(lexing)
将程序中的单词一个一个化解开来,划分出的单词是让js引擎可以识别的如var a = 1
2.语义化(pasing)
将刚才的一个个单词,组成抽象语法树AST(Abstract syntax Tree),刚才的var a =1就可能成为
declaringElement为父节点 identifierElement为子节点 alignMent为子节点 valueMent为子节点
3.代码生成
将刚才的AST转化成可以运行的代码,这个和语言目标以及各个平台有关系
简而言之,就是将var a = 1AST转化为计算机可以识别的指令 ,在内存中创建一个叫做a的变量,并将1存储其中
伏笔来了()
编译阶段,程序运行阶段
var a = 1实际上是两个阶段: var a是编译阶段的工作 a = 1程序运行阶段
1.遇到var a ,编译器首先会询问作用域是否已经有一个该名称的变量,存在于统一而作用域的集合中.如果是,编译器则会忽略该声明,继续编译,否则他会要求作用域在当前作用域的集合中声明一个变量,命名为a
2.接下来编译器会为引擎生成运行时候所需要的的代码,这些代码被用来处理 a= 2这个赋值操作.引擎运行时候会首先询问作用域,在当前作用域集合中是否存在一个叫做a的变量.如果是引擎就会使用这个变量,否则,引擎就会使用这个变量.
== 问题来了,引擎怎么查找刚才的那个变量的==
通过RHS查询和LHS查询