欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

函数预编译是怎么一回事?

程序员文章站 2022-07-14 12:14:46
...

JS执行三部曲:语法分析 预编译 解释执行
语法分析:简单来说就是浏览器先不运行代码,而是检查你代码是否有语法错误比如少加了符号啊,写入了中文字符等等操作。类似于老师检查你的作文,先不看你的内容怎么样,而是看有没有错别字。(啊哈哈,只是类比一下,好像有点不恰当,老师一般是直接一遍看过去)
预编译
预编译有两个抽象出来的知识点:

  • 函数声明整体提升
  • 变量 声明提升

函数声明整体提升指的是当你使用了函数声明,那么函数声明默认会提升至script顶部

		function up(){
          console.log("hello,world");
        }
       up();
       //打印hello,world

       up();
       //也能打印出hello,world
	 	function up(){
          console.log("hello,world");
        }
    

这就是说function up(){ console.log("hello,world"); }这个函数被默认提升到顶部,所以第二种方法执行函数的时候没有报错并且成功了。

变量 声明提升指的是变量在声明的时候,也会被提升至逻辑的顶端。

var a;
//这个叫做变量声明
var a = 1;
//这个叫做变量初始化 它相当于变量声明加变量赋值的双重操作
//即var a; a = 1;

我们需要注意的是提升的只有变量声明,也就是说:

console.log(a);
var a = 123;
//这里打印的是undefined而不是123

上面的操作拆解一下应该是这样:

var a;
console.log(a);
 a = 123;
//这里相当于只声明了变量a但是没有进行赋值,所以打印的是undefined

但是我们遇到声明的变量为a,声明的函数名也为a,那我们打印的a的时候打印的是哪一个呢?因为两者都有提升,哪一个更加优先呢?

		console.log(a);
		//a(){}
        function a(){
        }
        var a;

我们发现打印的是函数体a,但是这有可能是因为函数声明写在变量声明之前,那么我们颠倒一下顺序试一试:

 		console.log(a);
 		//a(){}
        var a = 123;
        function a(){
        }

我们发现结果没有变,还是打印的函数a,那么这个原理是什么呢?我们需要更深的去了解JS预编译的过程。

这里我们先了解两个知识点:

  1. imply global 暗示全局变量:即任何变量未经声明就赋值,那么这个变量就为全局对象所有。全局变量指的是window
		a = 123;
        console.log(a);
        //123
        console.log(window.a);
        //123

		var a = b = 123;
        console.log(window.a);
        //123
        console.log(window.b);
        //123

这里b变量未经声明就赋值所以变成了window的属性,但是a声明了为什么也变成了window的属性呢?这是因为下面一条规则
2. 一切声明的全局变量全部变为window的属性
上面代码的a是全局声明的,所以它也变成了window的属性。

 	  function go(){
         var a = b =123;
       }
       go();
       console.log(window.a);
       //undefined
       console.log(window.b);
       //123


而当我们在函数内部声明变量的时候 它就不是全局变量了,它变成了局部变量,就不满足第二条规则了,所以打印window.a的时候,结果显示的是undefined,而b还是未声明就赋值,所以它还是一个全局变量,因此打印了出来。

我们继续讲回预编译:
预编译,它发生在函数执行的前一刻,它有以下几个环节:

  1. 创建AO对象 Activation Object(执行期上下文)
  2. 找形参和变量声明,将变量和形参名作为AO对象的属性名,值为undefined
  3. 将实参形参相统一
  4. 在函数体里找到函数声明,把函数声明当做值赋予函数体
    让我们根据一个例子操作一下:
      function test(a) {
        console.log(a);
        console.log(b); 
        var b = 234;
        console.log(b); 
        a = 123;
        console.log(a);
        function a() {};
        var a;
        b = 234;
        var b = function() {};
        console.log(a); 
        console.log(b); 
        function d() {};
      }
      test(1);
  1. 创建AO对象
AO{
}
  1. 找形参和变量声明,将变量和形参名作为AO对象的属性名,值为undefined

我们首先寻找形参,发现函数的形参有a同时变量名它也有a我们只需要保留一个就行了,然后赋值undefined。

AO{
	a : undefined,
	b : undefined,
}
  1. 将实参形参相统一
    test(1)传的值是1
AO{
	a : 1,
	b : undefined,
}

4.在函数体里找到函数声明,把函数声明当做值赋予函数体。关于函数体,比如:

 	  function test() {
        console.log("hello");
      }
      test();
      console.log(test);
      // function test() {
        console.log("hello");
      }

这里我们打印test,输出的是test这整个函数,test就指代着函数体。
这里我们还需要注意的是 function fn(){}这种形式才叫做函数声明,而var fn = function(){}j叫做函数表达式

AO{
	a : function a (){},
	b : undefined,
	d : function d (){},
}

然后我们就得到了该函数完整的执行期上下文,然后以此为基础我们开始重新赋值

	  function test(a) {
        console.log(a); 1
        console.log(b); 2
        var b = 234;
        console.log(b);  3
        a = 123;
        console.log(a);  4
        function a() {};
        var a;
        b = 234;
        var b = function() {};
        console.log(a);  5
        console.log(b);  6
        function d() {};
      }
      test(1);
  1. 第一个打印的a根据AO对象我们知道应该是function a (){}
  2. 第一个打印的b我们根据AO对象知道应该是undefined
  3. 第二个打印的b我们这里要注意一下,因为上面有一句赋值语句:var b = 234;,所以这个时候打印的b变成了234
  4. 同理,第二个打印的a因为上面又进行了赋值,所以变成了123
  5. 第三个打印的a并没有重新赋值,所以还是123
  6. 第三个打印的b因为上面将函数体赋给了它,所以它变成了function (){}

好的,完成了!
那么接下来我们解决刚才的那个问题

		console.log(a);
		//a(){}
        function a(){
        }
        var a;

按照步骤首先创建AO对象

AO{
}

然后寻找形参和变量声明并赋值undefined

AO{
a : undefined
}

接着将实参形参相统一

最后在函数体里找到函数声明,把函数声明当做值赋予函数体

AO{
a : function a (){}
}

然后打印a,所以最后的结果是 function a (){}
这个其实应该用全局的预编译,具体环节和函数预编译差不多,只是我们发现好像第三步形参实参相统一多余了,所以我们全局预编译只有三步:

  1. 创建我们全局的执行上下文叫GO对象即(Global Object)
  2. 找变量声明,将变量名作为GO对象的属性名,值为undefined
  3. 最后在函数体里找到函数声明,把函数声明当做值赋予函数体
    我们的GO先于AO生成,并且首先元素查找AO,若AO没有则去GO查找
    用上面一个例子
	   function go(){
         var a = b =123;
       }
       go();
       console.log(window.a);
       //undefined
       console.log(window.b);
       //123

首先 ,我们应该先创建GO对象,由于b是一个全局变量所以创建GO对象的时候我们对其进行了初始化的操作最后等于123,因此我们在AO找不到b的时候去GO中寻找,最终打印出了结果。而a在AO对象中,且被赋值为undefined,所以最终输出也为undefined。
解释执行:预编译结束之后,就是我们浏览器的解释执行了。