JS作用域

169 阅读5分钟

01 作用域原理的五个阶段

  1. 编译(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转换成机器能识别的指令

  1. 执行
var a = 2/*1.执行阶段:引擎运行代码时首先查找当前作用域,查看是否有a变量,
如果有直接调用;如果没有,将继续查找a变量;
  2.如果找到该变量,将2赋值给a,否则报错
  1. 查询
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);
  1. 嵌套 (重要)
//作用域变量的查找机制
function fn(a){
    console.log(a + b);//找不到b 会到外层嵌套作用域查找
}
var b =2;
fn(2);

小结:在当前作用域找不到某个变量时,引擎会到外层嵌套的作用域继续查找,直到找到。最外可至最外层的全局作用域。

  1. 异常
//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 函数的执行环境与执行流

执行环境

以上段代码为例

  1. fn(0)的执行环境有
    x: 0 ; a:undefined ; fn2:function ; arguments:[0] ; this:window
  2. 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 执行环境栈

还是以上段代码为例

  1. 将执行环境作为一个栈
  2. 首先全局作用域入栈 进入到栈底部
  3. 接下来fn()函数入栈 压在全局作用域的上方
  4. 最后fn2()函数入栈 在fn()函数的上方
  5. fn2()函数执行完毕被释放 出栈
  6. fn()函数执行完毕被释放 出栈
  7. 整个程序代码执行完 全局作用域出栈