JavaScript是一门单线程,解释型,弱类型的动态语言,解释一行执行一行。
JavaScript执行过程首先先语法分析,就是分析一遍代码有没有语法错误,解析期间不会执行代码。接着就开始预编译,预编译完了就开始一行一行执行代码。
预编译过程会创建两个对象,一个是全局的Global Object对象,简写GO,另一个是函数的Activation Object对象,简写AO。两个只是作用域不同,创建步骤是一样的。
预编译大概步骤:
创建AO、GO对象
找形参和变量声明,作为属性名,值为undefined
统一实参和形参
找函数声明,赋值函数体
说的抽象了,我们以一个函数为例:
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(){}、1、1、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)
(完)