今天刚刚学习了JS预编译和函数作用域链,所以分享一下有关预编译及作用域链的理解。但是首先我们得需要先来了解一下关于JS引擎在解析脚本的过程中的两个阶段,预编译和执行,首先预编译然后再从上之下一行一行的执行代码。其次,要了解作用域,作用域是一个变量或者函数能够使用的空间,分为全局作用域和局部作用域,全局变量的作用域为全局作用域,局部变量(函数内部)的作用域为局部作用域。
全局作用域: 全局作用域在我个人的理解上来说就是在函数以外的区域都称之为全局作用域,所谓的全局变量也正是除函数体内以外的变量都是为全局变量,在整个window 对象下面: 所有数据变量都属于 window 对象,换言之Window就是最大的全局作用域。
局部(函数)作用域: 也称为执行期上下文,当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁(这个就是函数的生命周期)。它定义了变量或函数有权访问的其他数据,决定了它们的各自行为。
注意:
在执行程序前:将所有var声明的变量进行声明提前以及function声明的函数体整体提前(提前到当前作用域 全局作用域/函数作用域的顶部)集中创建,赋值留在原地!!!;
全局预编译:
全局预编译分为四步:
- 生成GO对象(Global Object)
GO: { } - 查找形参和变量声明(这里不是函数,因此没有查找形参这一步),值为undfined;
GO: { a: undefined } // 注意这里因为b是没有声明的所以不会被放到GO这个活动对象里面 - 将实参和形参相统一,值为实参里面的值值为实参里面的值(在全局环境依然没有这一步)
- 找函数声明,属性值为声明时候的属性值,GO: { a: undefined, test: function () {} }
这里需要特别注意的是:
- 在全局的变量声明必须是var 关键字的变量声明;
- 函数必须是function关键字定义的函数;
- 函数体内部没有任何关键字的变量也会提升到全局对象中;
- 函数的权限大于变量,当同名时函数会覆盖变量,不关前后出现顺序;
函数预编译:
函数的预编译相对复杂一点。函数的预编译发生在函数执行的前一刻,或者说函数执行时首先进行预编译。
函数预编译分为四步:
- 创建activation Object(执行期上下文 ),AO对象;
- 找形参和变量声明,将形参名和变量名作为AO对象的属性,属性值为undefined;
- 将形参值和实参值统一;
- 在函数体内找关键字函数(function关键字定义的子函数),将函数名作为AO对象的属性,
属性值为函数体;
这里需要特别注意的是:
- 函数执行完以后会销毁自己的执行期上下文;
- 实参和形参值统一在关键字函数之前,函数的权限比变量大,因此形参和子函数同名时也会被覆盖;
作用域链:
JS中函数自身也是个对象,函数对象自身有一个属性scope,scope中存储着函数执行期上下文对象的集合,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JavaScript引擎存取。这个对象成链式链接,这也就是函数的作用域链。
全局函数在声明提升时将全局的GO放入自己的scope,然后执行时放入自己的AO。
局部函数在声明提升时先将父级的scope放入自身的scope中,然后在执行时将自己的AO放入scope。注意对象的存储是堆存储,指向关系,内存中GO只有一个,函数及子函数指向GO只是在栈中创造空间,将GO的索引存入。子函数存储父级的scope也相同。函数执行完成后销毁自身的执行期上下文AO,但是声明提升时放入scope中的内容并不会销毁。