01 作用域原理的五个阶段
- 编译(JS没编译过程 JS是边解释边执行)
var a = 2;
//1.1分词
//词法单元:var, a, =, 2, ;
/*
{
"var":"keyord" //关键字
"a": "indentifier" //标识符
"=" : "assignment" //分配
"2": "interger" //整数
";": "eos" //结束语句
}
/*
//1.2解析
//抽象语法树 (AST Abstract Snatax Tree)
//将var a分为一部分,"="分为中间部分,2分为一部分
//1.3代码生成
//将AST转换成可执行的代码的过程,转换成机器指令
小结:编译过程就是编译器把程序分解成词法单元,解析成抽象语法树,再把AST转换成机器能识别的指令
- 执行
var a = 2;
/*1.执行阶段:引擎运行代码时首先查找当前作用域,查看是否有a变量,
如果有直接调用;如果没有,将继续查找a变量;
2.如果找到该变量,将2赋值给a,否则报错
- 查询
var a = fn();
function fn(){
return 1;
}
// a 属于LHS查询 FN()属于RHS查询 区别在于等号的左右侧
function fn2(a){
console.log(a);
}
fn2(2);
//1.fn2()对fn2函数对象进行了RHS引用(查询)
//2.函数传参a=2,对a进行了LHS引用
//3.console.log(a);对console对象进行RHS引用,并检查是否有log()方法
//4.console.log(a);对a进行RHS引用,把得到的值传给console.log(a);
- 嵌套 (重要)
//作用域变量的查找机制
function fn(a){
console.log(a + b);//找不到b 会到外层嵌套作用域查找
}
var b =2;
fn(2);
小结:在当前作用域找不到某个变量时,引擎会到外层嵌套的作用域继续查找,直到找到。最外可至最外层的全局作用域。
- 异常
//RHS查询异常
function fn(a){
a = b;//b is not defined
}
fn(1);
function fn2(){
var b =0;
b();//b is not a function
}
function fn3(){
a = 1;
}
console.log(a);//a is not defined
/*如果用fn3();调用了该函数 根据语句将会在全局作用域下创建
一个变量a并赋值1
/*
02 作用域的遮蔽现象
因为作用域查找是从运行时所处的作用域开始查找,逐级向上进行,直到查找到匹配的标识符为止
在多层嵌套作用域可以定义同名的标识符,这就是遮蔽现象
var a = 0;
function fn(){
var a =1;
console.log(a);//1
}
fn();
03 变量的声明提升
//预解释
/*a = 2;
var = a;
console.log(a); 打印出来是2 */
console.log(a);//console.log(a) 打印出来的结果undefined
//打印的时候会去查找全局作用域下有无a
//会在这行代码的上面添加一行代码 var a;
var a = 2;//所以其实只是运行了var a;
//声明从他们在代码中出现的位置被移动到当前作用域的最上面,
//这个过程叫变量提升,预解释
04 函数的声明提升
fn();
function fn(){
console.log(1);//打印出来的1
}
fn();
var fn = function (){
console.log(1);//打印出来的是fn is not a function
}
原因是有变量 执行到fn();的时候引擎会在当前作用于最上面自动生成var fn;
fn2();
var fn2 = function fn(){
console.log(1);//打印出来的是fn is not a function
}
同理会在最上面生成 var fn;
05 声明注意事项
var a;
function a(){};
console.log(a);//打印的是 f a(){}
//变量的声明优先于函数的声明,但是函数的声明会覆盖未定义的同名的变量
var a = 10;
function a(){};
console.log(a);//10
//函数的重复声明会覆盖前面的所有声明(包括函数和变量)
//函数的声明提升优先于变量的声明提升。
//(就是函声明提升会覆盖变量声明提升)
//这段代码实际运行为
var a;
function a(){};
a = 10;//所以才打印出10
//这也是为什么函数的声明会覆盖未定义的同名的变量原因 (因为没有赋值这一步骤哦)
var a = 1;
var a =10;
//它的实现原理其实是
var a;
var a ;
a= 1;
a= 10;
function fn(){
console.log('fn');
}
function fn(){
console.log('fn2');
}
fn();//打印出fn2
//后面的函数声明覆盖前面的函数声明
06 函数的作用域链与自由变量
var a = 1;
var b = 2;
// fn => (指向) 全局
function fn(x){
var a =10;
// fn2 => fn => 全局
function fn2(x){
// 自由变量:在当前作用域中存在但未在当前作用域中声明的变量
// 出现自由变量后,就会有作用域链,会根据作用域链机制查找变量
// 查找机制:沿着作用域链往上级查找直到找到变量,如果找不到,报错
var a = 100;
b = x + a;
return b;
}
fn2(20);
fn2(200);
}
fn(0);
07 函数的执行环境与执行流
执行环境
以上段代码为例
- fn(0)的执行环境有
x: 0 ; a:undefined ; fn2:function ; arguments:[0] ; this:window - fn2(20)的执行环境有
x: 20 ; a:undefined ; 全局中的b:2 ;arguments:[0] ; this:window
执行流
以上段代码为例执行顺序为
var a = 1;
var b = 2;
fn(0);
var a =10;
fn2(20);
var a = 100;
b = x + a;
return b;
fn2(200);
var a = 100;
b = x + a;
return b;
} (该中括号为fn()的中括号)
小结:执行环境指的是函数运行时所需要的所有的必要因素
08 执行环境栈
还是以上段代码为例
- 将执行环境作为一个栈
- 首先全局作用域入栈 进入到栈底部
- 接下来fn()函数入栈 压在全局作用域的上方
- 最后fn2()函数入栈 在fn()函数的上方
- fn2()函数执行完毕被释放 出栈
- fn()函数执行完毕被释放 出栈
- 整个程序代码执行完 全局作用域出栈