js的两个特点:
- 当我们在后面定义了一个函数之后,在定义之前可以使用这个函数。
- 当我们在后面声明的一个变量,但是在前面调用这个变量的时候并不会报错而是undefiend。但是你从未声明过直接用就会报错。
为什么会有这一特点,变量声明提升。
为什么会有这一提升现象,是因为js运行的时候有一阶段叫做预编译。
js运行三部曲:
- 语法分析。
- 预编译。
- 解释执行。
语法分析:js在执行前会通篇扫描一下,找出低级的语法错误,包含在方法内的错误。
编译执行:js是一种解释型语言,编译一行执行一行,当语法分析没有问题,并且已经完成预编译阶段之后,就开始解释执行代码。
下面我们着重介绍预编译,先了解两个概念。
- 如果任何变量未经声明就赋值使用,此变量就会为全局对象window所有,并且成为window对象的一个属性。
- 一切声明的全局变量,都是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';
}全局预编译
- 拿到当前的Global Object对象。
- 将变量声明提升,不包括变量赋值,值赋予undefined。
- 将函数声明提升,值赋予函数体。
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只是变量声明,并不是函数声明。函数预编译
- 创建Active Obejct对象。
- 将形参和变量声明添加到AO属性中,值为undefined。
- 将形参和实参相统一,就是将形参赋值。
- 在函数体里面找函数声明,函数名为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内的属性变成了闭包的属性私有,原本应释放掉的内存没有释放掉便造成了内存泄漏。