预编译

235 阅读5分钟

JavaSCript

javascript通常被归类为“动态”或者“解释执行”语言,其引擎进行编译的步骤与传统的编译编译语言相似,但是 也有不同的地方。传统编译语言的一段源代码执行之前会经历三部曲:分词/词法分析、解析/语法分析、代码生 成。而javascript相比之下,要略微复杂一些。javascript在代码执行之前的很短时间内,javascript引擎会 为后面的代码执行创造最优的条件,来保证性能最佳,这就是预编译环节

代码是怎么运行的?

  1. 在执行过程中,若使用未声明的变量,js执行会报错
  2. 在一个变量定义之前使用它,不会报错,但是该变量的值为undefined,而不是定义的值
  3. 在一个函数定义之前使用它,是不会报错,且函数能正确执行
//直接打印没有声明的变量
console.log(Name)//报错

console.log(myName);//undefined
var myName = 'abc'

showName() //hello
function showName(){
    console.log('hello');
}

变量提升

javaScript 代码在执行过程中,JavaScript引擎会把变量声明部分和函数声明部分提升到代码的最前面的“行为”

就比如上述代码中的var myName = ‘abc’ 并且打印myName。实际上javaCript在其执行前,会将其编译成如下所示:先声明变量,打印操作,最后是赋值,所以显然由于找不到值,打印结果为undefined

var myName;

console.log(myName);//undefined

myName = 'abc'

我们再看一个函数实例:在函数声明之前调用它也不会报错或者也不会找不到值是为什么。这个时候根据变量提升的规则就明白了,在函数执行前,JavaScript引擎将其编译成如下面代码展示的一样,将函数声明提前到代码打印操作执行之前,就跟正常写在前面一样,这就是JavaScript的编译机制。

function showName(){

    console.log('hello');
    
}

showName() //hello

预编译

函数预编译

预编译发生在函数执行的前一刻(四部曲)(函数体)

  1. 创建AO对象(Activation Object)

  2. 找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined

  3. 将实参和形参值统一

  4. 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体

function fn(a) {

    console.log(a);//[Function:a]
    
    var a = 123
    
    console.log(a);//123
    
    function a() {}
    
    console.log(a);//123
    
    var b =function(){}

    console.log(b);//[Function:b]

    function d(){}

}

// AO:{

//     a: undefined 1 function a(){} 123,

//     b: undefined function(){},

//     d: function d(){}

// }

fn(1)

根据四部曲,我们自己来模仿一下javaScript引擎的预编译机制: 首先,在函数被执行时,生成一个上下文对象AO:{ },找形参a,以及变量声明b,将变量声明和形参作为AO的属性名,值为undefined。而后我们将实参与形参统一,按照其顺序,首先a作为形参1,之后声明为function,最后赋值为了123,所以最终a = 123,同样b也是如此,直接声明为函数,所以打印之后就是[Function:b].在寻找函数声明,将函数名d作为AO对象的属性名,值赋予函数体,也就是d: function d(){}。而一步步按照顺序执行,便可以得到正确的结果

function test(a,b){

    console.log(a)//1

    c = 0

    var c;

    a = 3

    b = 2

    console.log(b)//2

    function b(){}

    function d(){}

    console.log(b)//2

}

// AO:{

//     a:undefined 1 3,

//     b:undefined function b(){} 2,

//     c:undefined 0,

//     d:function b(){}

// }

test(1)

上述代码也是类似的,可以按照函数预编译四部曲,自己试着推导一下。

全局预编译

预编译也发生在全局(三部曲)

  1. 创建GO对象

  2. 找形参和变量声明,将变量声明和形参作为GO的属性名,值为undefined

  3. 在全局里找函数声明,将函数名作为GO对象的属性名,值赋予函数体

// GO:{

//     global: undefined 100,

//     fn:function (){}

// }

var global = 100

function fn(){

    console.log(global);

}

// AO:{
   
// }

fn()


上述代码便是在全局进行预编译,在首先全局上下文GO:{ }中,有全局变量global,先赋值为undefined,在此没有形参,所以统一之后global就被赋值为100,最后再是函数function fn().不过在此注意的是,fn也会生成一个执行上下文对象AO:{ },再根据函数预编译四部曲,很显然里面为空,所以global上述结果为100.

结合

在js中,在任意的代码块中添加包装函数(如下例中的function foo()就是包装函数),可以将内部的变量和函数定义“隐藏”起来,私有化函数和变量,也就是说,函数访问外部作用域是单向通道,外部作用域无法访问包装函数内部的任何内容,

var = 2;

function foo() {

var a = 3;

console.log(a); //3

}

foo()

console.log(a); //2

下面的代码也是一样,由于fn中又声明了一个变量global,内部的global打印事关函数fn().预编译环节,函数体中声明变量global,值赋为undefined,此时打印global,结果就是undefined、之后赋值为200,那么打印结果自然为200.

global = 100

function fn(){

    console.log(global);//undefined

    global = 200

    console.log(global);//200

    var global = 300

}

fn()

var global

希望这些对学习js的小伙伴有用,谢谢!