JS预编译四部曲

JavaScript是解释性语言:解释一行,执行一行。
JS运行三部曲:

  1. 词法分析:
    虽然JS是解释一行执行一行,但在解释执行之前会首先通篇扫描一遍查看有没有低级语法错误。
  2. 预编译:
    发生在代码执行的前一刻。
  3. 解释执行:
    开始执行代码,解释一行执行一行。

预编译初识:

var a = 123;
console.log(a);
//控制台打印123;
console.log(a);
var a = 123;
//控制台打印undefined;
console.log(a);
//报错!Uncaught ReferenceError: a is not defined!(因变量a没定义)

这是由于预编译的作用:

  1. 变量 声明提升(var 变量名 );
    如上述第二段代码:
console.log(a);
var a = 123;

//经过预编译,其相当于:
var a; //变量声明提升,但此时还没有赋值
console.log(a); //打印a,由于没有赋值故打印undefined
a = 123;//打印完后,才把123赋给变量a
  1. 函数声明整体提升(执行永远在定义后);
    即函数声明不管写在代码视觉上看到的什么位置,系统总会将函数声明提升到逻辑最顶端。
    但是!要注意 函数表达式(var 变量名 = function() {} )和函数声明(function 变量名(){} )的区别!函数表达式不会提升。

预编译前奏:

  1. imply global 暗示全局变量
    任何变量,如果未经声明直接赋值,此变量就为全局对象所有。
function test() {
	var a = b = 123; 
	/*这里相当于:
	var a; 变量 声明提升
	b = 123; 变量b 没有声明!直接赋值的,归全局变量,在全局可以访问到。
	a = b; 相当于把123赋给a,但变量a是在函数test里声明的局部变量,全局访问不到。
	*/
	console.log(a);//打印123
	console.log(b);//打印123
}
test();
console.log(a);//报错Uncaught ReferenceError: a is not defined.
console.log(b);//打印123
  1. 一切声明的全局变量,全是window的属性;
    window就是全局的域。var a = 123; ===> window.a = 123;

预编译四部曲

  • 函数体系里的预编译过程:
  1. 创建AO对象; (AO===Activation object)
  2. 找 形参 和 变量声明,将 变量 和 形参名 作为AO属性名,值为undefined;
  3. 将实参值和形参统一;
  4. 在函数体里面找 函数声明,值赋予函数体; (注意函数表达式不会提升)
  • 全局的预编译过程:
  1. 生成GO对象; (GO===window object)
  2. 找 变量声明,将 变量 作为AO属性名,值为undefined;
  3. 再找 函数声明,值赋予函数体;

一、函数体系里的预编译过程:

function test(a, b) {
   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); 
}
test(1);

预编译发生在函数执行的前一刻,根据预编译四部曲逐步分析:

  1. 创建AO对象;(Activation Object)执行期上下文
    AO { }
  1. 找 形参 和 变量声明,将 变量 和 形参名 作为AO属性名,值为undefined;
    AO {
    //这里a,b都既是形参也是变量声明,有一个作为属性名即可
    a : undefined,
    b : undefined
    }
  1. 将实参值和形参统一;
    AO {
    a : 1, //a为形参,1为实参
    b : undefined
    }
  1. 在函数体里面找 函数声明,值赋予函数体;
    AO{
    //a是函数声明,赋予函数体;注意b不是函数声明
    a : function a () {}
    b : undefined
    }

上边四步都结束之后,AO对象创建完成,才开始执行函数。执行test(1);这句代码的时候才会进入到函数test中,在执行时函数会先在创建好的AO对象中(上述第4步)找相应的变量。

源代码分析:

function test(a, b) {
   console.log(a); //打印function a () {}
   console.log(b); //打印undefined
   var b = 234; // (替换上述第4步中b的值变为234)
   console.log(b); // 打印234
   a = 123; // (替换上述第4步中a的值变为123)
   console.log(a); // 打印123
   function a () {} //预编译时函数声明已经提升了,忽略不看
   var a; //预编译时变量声明提升了,忽略不看
   b = 234; 
   var b = function () {} //函数表达式,不是函数声明不能提升(替换上述第4步中b的值234变为此函数)
   console.log(a); // 打印123
   console.log(b); //打印function () {}
}
test(1);

二、真正的预编译过程:

console.log(test); 
function test(test) {
   console.log(test); 
   var test = 234; 
   console.log(test); 
   function test() {} 
} 
test(1); 
var test = 123;
console.log(test);

先创建GO对象(Global Object),再创建AO对象(Activation Object)。
GO:先不看函数内部的代码

  1. 创建GO对象;
    GO { }
  1. 找 变量声明,将 变量 作为AO属性名,值为undefined;
    GO {
    test : undefined
    }
  1. 再找 函数声明,值赋予函数体;
    GO {
    test : function test(test) {
    // … 整个函数体 }
    }

GO对象创建完成,开始读代码,直到读到全局调用test函数时,函数预编译,开始创建AO对象(预编译发生在函数执行的前一刻)
AO:

  1. 创建AO对象;(Activation Object)执行期上下文
    AO { }
  1. 找 形参 和 变量声明,将 变量 和 形参名 作为AO属性名,值为undefined;
    AO {
    test : undefined
    }
  1. 将实参值和形参统一;
    AO {
    test : 1 //test是形参,1是实参
    }
  1. 再函数体里面找 函数声明,值赋予函数体;
    AO{
    //找到函数声明,test同名,替换掉原来的实参
    test : function test () {}
    }

AO对象创建完成后,才开始进入test函数执行函数内部的代码。执行test(1);这句代码的时候才会进入到函数test中。在执行函数内部代码时,会先在其AO{}对象中找相应的变量取值,若AO{}中没有,再去全局变量GO{}对象中找。

源代码分析:

console.log(test); //1.打印下边整个test函数(在GO中找)
function test(test) {//2.函数声明,整个函数包括函数体先不看
   console.log(test); //4.打印function test () {}; (在AO中找)
   var test = 234; //5.(替换AO中test的值变为234)
   console.log(test); //6.打印234
   function test() {} //已经提升,忽略
} 
test(1); //3.调用函数(执行之前预编译先创建AO)
var test = 123; //7.(替换GO中test的值变为234)
console.log(test);//8.打印123
发布了1 篇原创文章 · 获赞 1 · 访问量 60

猜你喜欢

转载自blog.csdn.net/qq_42301358/article/details/105232622