JavaScript是一门单线程,解释型,弱类型的动态语言,解释一行执行一行。函数
JavaScript执行过程首先先语法分析,就是分析一遍代码有没有语法错误,解析期间不会执行代码。接着就开始预编译,预编译完了就开始一行一行执行代码。线程
预编译过程会建立两个对象,一个是全局的Global Object对象,简写GO,另外一个是函数的Activation Object对象,简写AO。两个只是做用域不一样,建立步骤是同样的。cdn
预编译大概步骤:对象
建立AO、GO对象ip
找形参和变量声明,做为属性名,值为undefined作用域
统一实参和形参it
找函数声明,赋值函数体io
说的抽象了,咱们以一个函数为例:console
function fn(a) {编译
console.log(a);
var a = 1;
console.log(a);
function a() {};
console.log(a);
var b = function () {};
console.log(b);
function c() {}
}
fn(3);
建立AO = {}
把形参和变量声明做为属性名和赋值undefined
AO = {
a: undefined,//参数a var a function a
b: undefined,//var b
c :undefined,//function c
}
统一形参和实参
AO = {
a: 3,
b: undefined,//var b
c :undefined,//function c
}
找函数声明,赋值函数体
AO = {
a: function a(){},
b: undefined,//var b
c: function c(){},
}
接着就是一行一行执行了:
function fn(a) {
console.log(a);
var a = 1;
console.log(a);
function a() {};
console.log(a);
var b = function () {};
console.log(b);
function c() {}
}
fn(3);
AO = {
a: function a(){},
b: undefined,
c: function c(){},
}
当执行第一个打印的时候,打印出function,而后var a = 1的时候,声明已经声明过了,其实就a = 1,因此第二个打印是1,到了声明函数a的时候已是声明过的,再打印也是1,至于b和c就不用多说了。最后结果就是f a(){}、一、一、f(){}。
其实能够记住几个点,函数声明是总体提高,变量声明只是声明提高。还有,若是一个变量没有声明,那么默认就是window的:
(function fn() {
var a = b = 10;
}());
console.log(b);//10
console.log(a);//err
b没有直接var声明,那么就是全局window的,因此b能打印,a就会报错。
有个点要注意,JavaScript在预编译阶段, 会解释函数声明, 但却会忽略表式。好比一个自执行函数:
(function fn() {
}())
当执行到有()的时候,JavaScript会去对这个表达式求解获得返回值,返回的是一个函数且有(),因此直接执行了,其它的自执行函数原理都是这样的,都是经过表达式。函数转换为表达式的方法并不必定要靠分组操做符(),咱们还能够用void操做符,!操做符+操做符等等。
+function () {}()
void(function () {alert(0)}())
console.log(function () {alert(0)}())
这些表达式均可以当即执行函数,就算+号获得的最终结果是NaN,可是在隐式转换以前却要先执行函数。
函数参数你能够看做在函数里面隐式的声明了一个变量a:
function fn(a) {
var a;
console.log(a);//3
}
fn(3)
并且函数参数里面在预编译过程当中,会造成一个临时做用域,在预编译完了以后会消失:
function fn(a, b = function () {a = 5}) {
console.log(a);//3
b();
console.log(a);//3
}
fn(3)
(a, b = function () {a = 5})这是一个临时的做用域,这里面的参数a就算改变了也影响不到原来的参数a。只有在参数做用域里面才有效果:
function fn(a, b = (function () {a = 5})()) {
console.log(a);//5
console.log(a);//5
}
fn(3)