JS解析器执行原理和声明提升机制

JS解析器执行原理和声明提升机制

一.概念

浏览器中有一套专门解析JS代码的程序,这个程序称为JS的解析器

浏览器运行整个页面文档时,遇到< script > 标签时JS解析器开始解析JS代码

二.JS解析器的工作步骤

1.预解析代码

主要找一些关键字如 var,function,以及函数的参数等,并存储进仓库里面,也就是内存

先扫描全局的代码,在函数执行的时候,然后扫描局部的,也就是函数内部的

  • 变量的初始值是undefind

    //声明一个a变量,放进仓库scope
    console.log(a);//undefined
    var a = 1;
    
  • 函数的初始值就是该函数的代码块(而不是undefined)

    console.log(test);//打印函数代码块
    //声明一个函数,放进仓库scope
    function test() {...}
    
    
    //控制台打印函数代码块
    ƒ test() {
        return 1;
    }
    
  • 当变量和函数重名时,不管顺序谁前谁后,只留下函数的值

    console.log(test);//打印函数代码块
    function test() {
        return 1;
    }
    var test=2;
    
    //控制台打印函数代码块
    ƒ test() {
        return 1;
    }
    

    注意:这里是预解析的时候,函数是一等公民,比变量优先级高

  • 当函数和函数重名的时候,只会留下后面的那个函数,会遵从上下文机制

    console.log(test);
    function test() {
        return 1;
    }
    
    //重名时,预解析只会留下后面这个函数
    function test() {
        return 234;
    }
    var test=2;
    
    //控制台打印函数代码块
    ƒ test() {
        return 234;
    }
    

2.逐行执行代码

当预解析完成之后,就开始执行代码,仓库中变量的值,随时都可能发生变化

1. alert(a);// function a(){alert(3);}
2. var a = 1;
3. alert(a);//1
4. function a() { alert(2); }
5. alert(a);//1
6. var a = 3;
7. alert(a);//3
8. function a() { alert(3); }
9. alert(a);//3

/*
解读代码
  预解析过程:	
  		第2行:找到了一个var 关键字,声明一个变量a
  		第4行:找到了一个function关键字 把a变成了一个函数
  		第6行:找到了一个var关键字,但是由于声明变量名字与函数重名,所以不起作用,a的值还是一个函数,因为
  			 函数比变量的优先级高
  	    第8行:找到了一个function关键字,a还是一个函数,但是把第4行的给覆盖了,因为函数重名,会遵从上下
  	         文机制
		
	    最后预解析得到的结果是:
        a => fn  -> function a(){alert(3);}
        
  原因:  1.当变量和函数重名时,不管顺序谁前谁后,只留下函数的值
  	    2.当函数和函数重名的时候,只会留下后面的那个函数,会遵从上下文机制
  	  
  逐行代码执行过程:
		 第1行: 弹出最后的名字叫a的函数代码块
		 alert(a);// function a(){alert(3);}
		 第3行: 因为第2行将a从函数变成了变量,并赋值为1
		 alert(a);//1
		 第5行: 因为第4行只是声明了一个函数,不会自己执行,所以a的值保持不变
		 alert(a);//1
		 第7行:因为第6行将a的值变成了3
		 alert(a);//3
		 第9行:因为第8行只是声明一函数,不会自己执行,所以a的值保持不变
*/

3. 示例解读

像这种示例,如果一眼无法看出来的话,使用解析器原理去看待题目,就没有解析不出来的,如果熟了,基本上就一眼就可以看出来,因为一步一步解析太麻烦了.

3.1 示例1(全局变量和函数内部变量名相同)
1.var a = 1;
2.function test(x) {
3.    alert(x);
4.    alert(a);
5.    var a = 2;
6.    alert(a);
7. }
8. test();

/*
  解读代码
  预解析过程:扫描代码,寻找var,function,以及函数的参数,放进仓库
  		   先扫描全局的var function
 		   global scope=>{
 		  		a => undefined
 		  		text =>function test(){} 
 		   }
   逐行代码执行过程:
   		 	第1行,a赋值为1
   		 	第2行,只是声明函数,不会执行
   		 	第8行,调用函数,开始进行局部扫描,也就是函数内部进行预解析
   		 	test scope=>{
   		 		x => undefined
   		 		a => undefined
   		 	}
   		 	test()逐行执行函数内部的代码:
   		 	第3行:alert(x)=> undefined
   		 	第4行:alert(a)=> undefined
   		 	第5行,a赋值为2
   		 	第6行:alert(a)=> 2
*/
3.2 示例2 (变量和函数重名)

当变量和函数重名时,不管顺序谁前谁后,只留下函数的值

1.alert(typeof fn);//function
2.var fn = 10;
3.function fn() { };
4.alert(typeof fn);//number

/*
解析代码
  预解析:
      global  scope=>{
          fn=> function fn() { };
      }
      
   执行代码:
      1.alert(typeof fn); 会弹出function
      2.赋值fn=10,此时fn类型变成了number
      3.不会执行
      4.alert(typeof fn); //number
*/
3.3 示例3(函数内部使用全局变量)
1.var a = 1;
2.function fn() {
3.    alert(a);//1
4.    a = 2;
5.}
6.fn();
7.alert(a);//2

/*
解析代码
  预解析:
      global  scope=>{
      	  a=>undefined
          fn=> function fn() { };
          
          fn scope=>{
          	 a=>undefined
          }
      }
      
   执行代码:
      1.a赋值等于1
      2.不会执行
      6.执行函数,函数没有参数,也没有var关键字进行声明,不会预解析
      3.第3行:alert(a) 访问的是全局变量 a  弹出1
      4.第4行:将全局变量a 赋值为了2
      7.第7行:弹出2
*/
3.4 示例4(局部变量不会改变全局变量)
1.var a = 1;
2.function fn(a) {
3.    alert(a);//undefined
4.    a = 2;
5.    alert(a);//2
6.}
7.fn();
8.alert(a);//1

/*
解析代码
    预解析:
    	global scope=>{
    		a=> undefined
    		fn=>function fn(a) {...}
    		
    		fn scope=>{
    		   a=>undefined//预解析形参
    		}
    		
    	}
     执行代码:
        先调用fn函数 fn函数内部弹出a为undefined
        函数内部 a赋值为2
        函数内部 执行第二个alert(a) 为2
        
        函数执行完之后,继续执行下一步 alert(a) 为1因为函数函数内部虽然给了a=2
        但是至少改的函数内部的值,不会改全局变量 的a
        
*/
3.5 示例5
console.log(num);// undefined
var num = 24;
console.log(num);// 24
func(100, 200);
function func(num1, num2) {
    var total = num1 + num2;
    console.log(total);// 300
}

很自然的一段代码,不要受前面的影响,而不知道最最简单的代码了

3.6 示例6.(同名函数)
fn();//2
function fn() { console.log(1); }
fn();//2
var fn = 10;
fn();//报错 fn is not a function
function fn() { console.log(2); }
fn();

/*
解析代码
   预解析:
   global scope=>{
   	  fn =>function fn() { console.log(2); }
   }
   
   同名函数时,后面覆盖前面
   变量和函数同名时,函数是一等公民,优先级高
   
   逐行执行代码时:
   第4行:将fn变成了一个number类型,再执行fn()时,函数不存在了,会直接报错,停止执行
*/

三.声明提升机制

在 JavaScrip 中变量声明和函数声明,声明会被提升到当前作用域的顶部。

讲声明提升机制之前,要先说说JavaScript中的作用域,这里排除ES6

1.作用域

在Javascript中,作用域为可访问变量(包含对象和函数)的集合

也就是说:作用域就是起作用的范围

1.全局作用域

整个页面起作用,在script内部都能访问到

全局作用域中有全局对象window,代表一个浏览器窗口,可以直接调用

全局作用域中声明的变量和函数,会作为window对象的属性和方法保存

变量在所有函数外声明,也就是全局变量,拥有全局作用域

<script>
    var a = 123;//全局变量
    function fn() {
        console.log(a);//123
    }
    fn();
    console.log(a);//123
</script>

在JavaScript中,函数是唯一拥有自身作用域的代码块

2.局部作用域

局部作用域内的变量只能在函数内部使用,所以也叫函数作用域

变量在函数内声明,即为局部变量,拥有局部作用域

<script>
    function fn() {
    	var a = 123;//全局变量
        console.log(a);//123
    }
    fn();
    console.log(a);//报错:Uncaught ReferenceError: a is not defined
</script>

注意:

  • 可以直接给一个未声明的变量赋值(全局变量),但不能直接使用未声明的变量!
  • 由于局部变量只作用于函数内部,所以不同的函数内部可以相同名称的变量
  • 当全局与局部有同名变量的时候,访问该变量将遵循"就近原则"

2.变量的生命周期

全局变量在页面打开时创建,在页面关闭后销毁

局部变量在函数开始执行时创建,函数执行完之后局部变量自动销毁

3.声明提升机制

JavaScript 的变量声明具有声明提升机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的顶部

我们可以从JavaScript解析器的执行原理来理解声明提升机制,就很好理解了,同样,上述解析器执行的示例代码中,都是声明提升机制的一种体现.当然了,这里是不考虑ES6的.

总结一下:

  • javascript是没有块级作用域的,函数是javascript中唯一拥有自身作用域的结构

  • 声明变量,实际上就是定义了一个名字,在内存中开辟了存储空间,并且初始为undefined,提升到当前作用域顶部

  • 函数的参数是原始类型的值(数值,字符串,布尔值),传值方式是传值传递,也就是在函数体内修改参数值,不会影响到函数外部

    var a = 1;
    function fn(a) {
        console.log(a);//3
    }
    fn(3);
    console.log(a);//1 不会改变原始值
    
  • 函数的参数是复合类型,也就是引用类型(数组,对象,函数)的时候,传值方式是传址传递,传入的是原始值的地址,所以在函数内部修改参数,会改变原始的值(可参考深浅拷贝原理)

    var obj = { a: 1, b: 2 };
    function fn(obj) {
        obj.a=2;
    }
    fn(obj);
    console.log(obj);//{a: 2, b: 2}
    

    注意:如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值!

    比如数组:

    var arr = [1, 2, 3]
    function fn(array) 
        array = [1, 2];
    }
    fn(arr);
    console.log(arr);//[1, 2, 3]
    

    4.声明提升机制示例

    • var a 变量声明,提升到作用域顶部,
    • var a=1; 变量声明,提升到作用域顶部,但是赋值部分不会被提升
    • var a=function(){ … } 函数表达式,但也只是变量的声明提升,并不会提升函数值
    • function a() {…} 函数声明,会全部提升,而且如果函数名字和变量名字相同的话.优先级比变量高,因为是一等公民

    总之呢,理解声明提升机制,好好去理解JS解析器的原理就好了.

猜你喜欢

转载自blog.csdn.net/liuqiao0327/article/details/106971270
今日推荐