写在前面
三月结束了,四月开始了。希望这个世界早点好起来。
今天是个好日子,好呀嘛好日子。哈哈哈哈哈哈,省里发文件了,终于让我看到了开学的希望,心情格外的舒适。
好了好了,来说说今天都干嘛了吧。今天又了解了一些JS预解析的内容,跟第一次看到的有一些出入,今天来补充一下。也没有很大的区别,主要就是更深入的理解JS预编译的过程。开始总结学习了啊啊啊啊啊啊。
JS代码执行过程
我们都知道,JS语言是一种单线程解释性语言。它是一门语言,它有它自己的执行机制。来看下吧。
JS代码执行过程分三个过程
1. 首先,进行语法分析,语法分析就是浏览器先通栏浏览一下整体的代码有没有少写分号、单词拼错等语法的错误。
2. JS预编译。本质是创建AO对象或GO对象,对其属性的操作。
3. 最后解释性执行。就是一行一行的读取代码执行代码。
JS代码执行的过程就是这三个步骤,需要着重理解的就是JS中的预编译的问题。
JS预编译
预编译简单理解就是,在代码执行前一刻发生的事情,我们要做到的就是弄清楚具体发生了哪些事情,是怎么发生的,也就是理解JS预编译的过程。全局域和局部域都有预编译的发生,逐个讲解了。以下内容是综合我看到的知识和个人理解的。
函数的JS预编译过程
这里需要先解释一个概念——AO对象。
AO对象:Activation Object,指活动性对象,也叫执行期上下文,就是我们通常所说的作用域。这里指函数的局部作用域。
来个简单的例子体验一下。
function sum(a, b) {
console.log(a);
var a = 10;
console.log(a);
var b = 5;
function arr() {}; // 函数声明
console.log(b);
console.log(arr);
}
// 调用函数
sum(2, 2);
案例函数的JS预编译的过程。
调用时,先在函数内部进行js预解析
1. 先创建AO对象 AO{}
2. 把形参和变量声明作为对象的属性
AO {
a: undefined // 形参a和变量声明的a一样,只需写一个
b: undefined
}
3. 把实参和形参值统一
AO {
a: 2
b: 2
}
4. 在函数体中找函数声明,值赋为函数体
AO {
a: 2
b: 2
arr: function () {};
}
然后进行代码执行,从上往下执行。
function sum(a, b) {
console.log(a); // 去AO对象里找属性a对应的值 2
a = 10; // 10,到这一步时,AO对象中的a的值被10覆盖 (a:10)
console.log(a);
b = 5; // 到这一步,b覆盖掉原来的值2,变为5
console.log(b); // 5
console.log(arr); // function
}
// 调用函数
sum(2, 2);
执行结果
需要注意的点,就是当变量名和函数名一样时,AO对象就添加一个属性,代码往后执行时,属性值会被覆盖掉的。上面的例子就已经很好的解释说明了JS预编译的过程。最最最核心的就是,预编译时AO对象的属性对应的属性值,会在后续代码执行过程中发生变化,也就是会被覆盖。
这种过程跟之前我看的过程差不多,当时只是理解到把函数声明和变量提到当前作用域的最前面,没有考虑当变量名和函数名同名时,会遇到的问题。
全局预解析
同样的,还是先解释一个概念——GO对象。
GO:Gobel Object,是全局对象,GO对象跟window对象是同一个对象。可以理解为window对象有两个名字 window == GO。
简单的例子
var b = 33;
console.log(b); // 33
// 没有调用函数,不执行
function arr() { // 这个函数是函数声明
var a = 11;
var sum = 5;
}
b = function aaa() {}; // 这个是函数表达式 不是函数声明
// 执行到这一步时,变量b,在GO对象中,上面重新赋值了,所有b的值被新值覆盖
console.log(b); // function aaa(){}
console.log(a); // 报错。因为a 是函数内部的变量,属于局部变量,外部函数不能访问
预编译过程
同样进行预解析
1.创建GO对象 GO {}
2.把声明的变量给到GO的属性 赋值为undefined
GO {
b: undefined
}
3. 找全局域中的函数声明,放到GO对象的属性,赋值为函数体
GO {
b: undefined
arr: function aaa() {}
} 预解析完毕,执行代码
执行结果
全局域中的预编译,就是对变量和函数声明的提升,过程与函数的预编译很像,就是去掉了形参和实参那一步。
预编译案例
来个综合的,稍微复杂点的例子,练练手。
// 全局域
a = 100;
function demo(e) {
function e() {};
arguments[0] = 2;
console.log(e);
if (a) {
var b = 0;
}
var c;
c = function sum() {} // 函数表达式,不是函数声明
a = 10;
var a;
console.log(b);
f = 123;
console.log(c);
console.log(a);
}
var a;
demo(1);
console.log(a);
console.log(f);
先看结果
过程分析
首先是全局域中的预编译,这里只声明了一个变量a,和一个函数demo。所以GO对象
GO {
变量
a: undefined
函数声明
demo: function () {}
}
然后执行代码,到demo,调用demo函数。紧接着对demo函数进行预编译。
1 和2创建对象并把变量和形参提出来
AO {
形参
e: undefined
变量
b: undefined
c: undefined
a: undefined
}
3. 形参实参统一
AO {
形参
e: 1
变量
b: undefined
c: undefined
a: undefined
}
4. 找函数声明, 没有
AO {
形参
e: 1
变量
b: undefined
c: undefined
a: undefined
}
执行代码
这就是预编译的过程。最终作用域中的变量和函数都将以AO对象的属性或者GO对象的属性存储着,代码执行时,其中的属性值会随之变化的。好好理解理解,多分析分析过程就好了。
预编译完成后,接着进行代码的执行。
整个案例预编译完整过程
最后,附上,案例整个代码的预编译过程,以及其中属性值的变化。
整给代码预编译的过程,以及对象中属性和属性值的变化
下面的代码和注释,很重要!!!很重要!!!很重要!!!
function demo(e) {
// 1 和2 AO {
// 形参
// e: undefined
// 变量
// b: undefined
// c: undefined
// a: undefined
// }
// 3. 形参实参统一
// AO {
// 形参
// e: 1
// 变量
// b: undefined
// c: undefined
// a: undefined
// }
// 4. 找函数声明, 没有
// AO {
// 形参
// e: 1
// 变量
// b: undefined
// c: undefined
// a: undefined
// }
// 执行代码
function e() {};
arguments[0] = 2;
console.log(e); // 2 实参列表的第一个数 与 e映射,arguments[0] == e == 2
if (a) { // AO中a的值为undefined,转化为数值是NaN,所以下面代码不执行
var b = 0;
}
var c; // 忽略 因为预编译进行过了
c = function sum() {}
// AO {
// 形参
// e: 1
// 变量
// b: undefined
// c: function sum() {}
// a: undefined
// }
a = 10;
// AO {
// 形参
// e: 1
// 变量
// b: undefined
// c: function sum() {}
// a: 10
// }
var a; // 忽略
console.log(b); // undefined
f = 123; // 未声明的变量 全局变量,放到GO对象中
// GO {
// 变量
// a: 100
// 未声明的变量, 是全局变量
// f: 123
// 函数声明
// demo: function () {}
// }
console.log(c); // function sum() {}
console.log(a); // 10
}
var a; // 忽略了 ,GO进行过
demo(1);
// GO {
// 变量
// a: 100
// 未声明的变量, 是全局变量
// f: 123
// 函数声明
// demo: function () {}
// }
// AO {
// 形参
// e: 1
// 变量
// b: undefined
// c: function sum() {}
// a: 10
// }
console.log(a); // 100
console.log(f); // 123
当代码执行完demo(1)后,预编译最终的内容,也就是AO对象和GO对象里面存放了什么
GO {
变量
a: 100
未声明的变量, 是全局变量
f: 123
函数声明
demo: function () {}
}
AO {
形参
e: 1
变量
b: undefined
c: function sum() {}
a: 10
}
最后,在代码输出时,就直接从对象的属性中对应属性值即可。