知识点预习
1、js文件的执行三部曲:语法分析、预编译、解释执行。
2、变量未经声明就使用,系统会报错。
3、var aa = "夫_子":变量的声明和赋值,aa为变量名,"夫_子"是变量值。var aa是变量的声明,aa = "夫_子"是变量赋值。
4、函数声明:function demo(){}。函数表达式:var demo=function(){}
一、语法分析
js文件在执行的最开始,会将文件整体扫描一遍,检查是否含有最基本的语法错误,比如,含有中文符号、关键字单词拼写错误等等最基本的语法错误,此为语法分析。
二、预编译
1、在说预编译之前,先看三个有意思的现象
test();//夫_子
function test(){
console.log("夫_子");
}
test();//夫_子
console.log(a);//undefined
var a=123;
console.log(a);//123
b();//Uncaught TypeError: b is not a function
var b=function(){
console.log("夫_子");
}
b();//夫_子
console.log(c);//Uncaught ReferenceError: c is not defined
看上面的例子,函数的执行,不管是放在函数定义之前,还是函数定义之后,都能执行出正确的结果,但是如果按照js的语言执行规则来解释,却又解释不通, 例如我们看到这里test的执行在函数的上面,如果按照代码的执行顺序,应该先执行test,再创造函数,那么显然就会报错,但此处却没有任何问题。
第二个和第三个是变量的定义,变量a和b在定义之后执行,没有任何问题,但是在定义之前执行,却出现了两个不同的结果,a的执行结果是(undefined:未定义),没有报错,b的执行结果是(Uncaught ReferenceError: a is not defined:a没有定义),系统报错了。这一切都是为什么呢,为什么a不报错而b报错了呢,先别急,慢慢看,接下来才是重点。
造成这一切的根本原因,就是代码执行前期的预编译,那什么是预编译呢?
2、预编译
简单来说,预编译主要有两点:一是变量、声明提升,二是函数整体提升,提升到语句执行逻辑的最前边。赋值和逻辑操作会被留在原地等待执行,即使赋值语句即等号后边是一个函数体。这么说,可能还是一知半解,甚至是完全看不懂,接下来实例讲解。
3、实例讲解
预编译四部曲:
1、创建AO对象:执行期上下文(Activation Object)
2、寻找形参和变量声明,并且当做属性放在AO对象里,并赋值为undefined
3、将实参和形参统一
4、寻找函数声明,值为函数体(此过程权重最高,如果和变量同名,会用函数体覆盖变量)
function demo(a){
console.log(a);//function a(){}
var a="夫_子";
console.log(a);//夫_子
function a(){}
console.log(a);//夫_子
var b = function (){}//函数表达式
console.log(b); //function (){}
}
demo(1)
/*预编译期间,各变量值得变化
AO{
a:undefined -->1 -->function a(){},
b:undefined
}
*/
1、创建AO对象:执行期上下文(Activation Object)
AO{ }
2、寻找形参和变量声明,并且当做属性放在AO对象里,并赋值为undefined
AO{
a:undefined,
b:undefined
}
3、将实参和形参统一
AO{
a:1,
b:undefined
}
4、在函数体里寻找函数声明,值赋予函数体(此过程权重最高,如果和变量同名,会用函数体覆盖变量)
AO{
a:function a(){},
b:undefined
}
5、此函数在预编译结束的时候,AO里存储的对象就是如下,在执行函数的时候,再根据语句情况,修改相应的变量值
AO{
a:function a(){},
b:undefined
}
上边的例子可以相对简单一些,下面来一个稍微复杂点的
function test(a,b){
console.log(a);//5
c=0;
var c;
a=10;
b=20;
console.log(a);//10
console.log(b);//20
function b(){}
console.log(b);//20
}
test(5);
/*预编译期间各变量值得变化
AO{
a:undefined -->5;
b:undefined -->function b(){}
c:undefined
}
*/
1、创建AO对象:执行期上下文(Activation Object)
AO{ }
2、寻找形参和变量声明,并且当做属性放在AO对象里,并赋值为undefined
AO{
a:undefined,
b:undefined
c:undefined
}
3、将实参和形参统一
AO{
a:5,
b:undefined
c:undefined
}
4、在函数体里寻找函数声明,值赋予函数体
AO{
a:5,
b:function b(){}
c:undefined
}
5、此函数在预编译结束的时候,AO里存储的对象就是如下,在执行函数的时候,再根据语句情况,修改相应的变量值
AO{
a:5,
b:function b(){}
c:undefined
}
函数的预编译过程,按照上面的四个步骤来进行,就不会出错。函数的优先级要比变量的优先级高,这里的优先级不是谁在前边,谁在后边,而是在预编译的过程中,如果变量名字和函数名一样,那么函数体会覆盖变量的undefined。
此文涉及的变量,都是局部变量,关于函数里未经声明就赋值的变量和全局变量怎么处理,会在下一篇全局下的预编译中详细讲解
三、解释执行
js是一门解释性语言,解释型语言的程序没有运行前的编译过程,运行程序的时候才翻译,会有一个专门的解释器负责在每个语句执行的时候解释程序代码。解释一行执行一行。
函数在执行的前一刻,会创建一个独一无二的执行期上下文即AO,用来存储函数执行过程中需要用到的变量和函数声明,这个执行期上下文就是函数执行时的环境,在函数执行完毕之后,这个执行期上下文就会被销毁(闭包单独讲解),下一次调用函数的时候,又会重新创建一个新的执行期上下文。
函数在执行的时候,会根据函数的情况,修改AO中存储的变量值,例如:第一个例子中,函数执行到 var a="夫_子";语句时,会将AO中a的值修改为“夫_子”,有人可能会问,上边a的值是一个函数体function a(){},不是说,函数要比变量的优先级高么,那为什么a=“夫_子”还是会覆盖函数体。
在函数预编译的时候,函数的优先级要比变量的优先级高,但是在函数执行的过程中,变量的赋值语句会覆盖函数体。请看下边的例子,至于这些到底是为什么,这就是浏览器内核的解析规则了,
function a() {}
var a;
console.log(typeof a);//fuction,此时a指向函数。
var a;
function a() {}
console.log(typeof a);//function,此时a仍指向函数。
function a() {}
var a=5;
console.log(typeof a);//number,此时a等于5。
var a=5;
function a() {}
console.log(typeof a);//number,此时a等于5。
function test(a){
console.log(a);//f a(){}
var a=5;
console.log(a);//5
console.log(b);//f b(){}
var b=function(){};
console.log(b);//f (){}//和上一个b打印的函数,不是同一个函数
function a(){}
function b(){}
}
test(5)
此文是在看了腾讯课堂里的渡一教育的视屏课之后,结合自己的理解所写,如有错误,欢迎纠正!