js执行前-预编译

230 阅读4分钟

js的两个特点:

  1. 当我们在后面定义了一个函数之后,在定义之前可以使用这个函数。
  2. 当我们在后面声明的一个变量,但是在前面调用这个变量的时候并不会报错而是undefiend。但是你从未声明过直接用就会报错。

为什么会有这一特点,变量声明提升。

为什么会有这一提升现象,是因为js运行的时候有一阶段叫做预编译。

js运行三部曲:

  1. 语法分析。
  2. 预编译。
  3. 解释执行。

语法分析:js在执行前会通篇扫描一下,找出低级的语法错误,包含在方法内的错误。

编译执行:js是一种解释型语言,编译一行执行一行,当语法分析没有问题,并且已经完成预编译阶段之后,就开始解释执行代码。

下面我们着重介绍预编译,先了解两个概念。

  1. 如果任何变量未经声明就赋值使用,此变量就会为全局对象window所有,并且成为window对象的一个属性。
  2. 一切声明的全局变量,都是window的属性。

经过声明的全局变量不能通过delete操作来删除,但是未经声明的全局变量可以被删除。具体可以看《你不知道的JS》上。

window是浏览器环境js执行的顶层作用域,全局的域,根源上就是一个对象,一切声明的全局变量,都会被挂在window上,我们用的console,alert。

区别于node环境和浏览器环境,node环境的顶层域是global,并且没有window。浏览器中两者兼有,且相等。因此可以通过他们两个是否相等来区分当前js的执行环境。

题外话:

node的包管理是遵循commonJS规范,ES6后定义了自己的包管理规范。ES6的包管理思想是尽量的静态化,使得编译时就确定模块间的依赖关系。而CommonJS规范只能在运行时确定依赖关系。

// 所以在node中你可以这样写
if(...){
    module.exports = {}
}
// 但是在es6中这样会报错
if (...) {
    import { a } from 'module';
}

全局预编译

  1. 拿到当前的Global Object对象。
  2. 将变量声明提升,不包括变量赋值,值赋予undefined。
  3. 将函数声明提升,值赋予函数体。

console.warn(typeof(a)); // function
var a = 3;
function a (){};
// 这段代码打印出来是function,因为函数声明提升在第三步,优先级较高
 --------------------------------------------------------------------------------------------------------
console.warn(typeof(a));// undefined
var a = 3;
var a = function(){};
// 这段代码答打印是undefined,因为var a = function只是变量声明,并不是函数声明。

函数预编译

  1. 创建Active Obejct对象。
  2. 将形参和变量声明添加到AO属性中,值为undefined。
  3. 将形参和实参相统一,就是将形参赋值。
  4. 在函数体里面找函数声明,函数名为AO属性名,值赋予函数体。

AO内其实本就存在部分属性,比如this,arguments。

function test (a, b) {
     console.log a // function
     function a () {}
     a = 222;
     console.log a // 222
     function b () {};
     console.log b // function
     var b = 111;
     var a ;
}
test(1)
// 1 首先创建AO = {}
// 2 将形参和变量声明添加到AO属性中,值为undefined。
    AO = {
        a: undefined,
        b: undefined
    }
// 3 将形参和实参相统一,就是将形参赋值。
    AO = {
        a: 1,
        b: undefined
    }
// 4 在函数体里面找函数声明,函数名为AO属性名,值赋予函数体。
    AO = {
        a: function (),
        b: function ()
    }

这里我们需要注意一点的是:哪些语法会形成单独的作用域,而那些语法不会。if else不会,for循环不会,我们自己定义的方法肯定会,有些方法也会,比如map,foreach。

而没有形成单独作用域的语法,肯定也在本次函数预编译之内,具有自己的单独作用域的语法,会在执行时,进行自己的预编译。

联想到作用域链

GO,AO就是作用域。多层function执行,由此形成 GO→AO→AO→AO..........由此形成作用域链。

当一层函数执行过后,其AO理应被释放掉,由JS的垃圾回收机制进行回收,但是如果返回了function且依赖于本层的AO,由此形成了闭包,此时本层AO内的属性变成了闭包的属性私有,原本应释放掉的内存没有释放掉便造成了内存泄漏。