一、什么是预解释
当js引擎读到js文档的时候,在js代码执行之前,就先把所有带var和function的进行提前的生命或者定义(变量提升的原因。)。
1、理解声明和定义
声明就是要告诉浏览器中,这个变量是存在的,但是不代表这个变量是有对应的值。因此此时它有一个默认的值undefined。
var krysliang;
定义就是就是确切的给这个变量一个值,不管这个值是什么类型的。
krysliang = 1996;
2、对于带var和function关键字在预解释的时候操作
var:在预解释的时候只是提前声明。
function:在预解释的时候声明+定义都完成了。所有在js代码执行的时候,才能直接调用这个函数。
3、预解释只发生在当前的作用域下
就是当文档加载的时候,开始只对window进行预解释,函数执行的时候才会对函数里面的代码进行预解释。
二、作用域与闭包
1、什么是私有变量和全局变量。
1)在全局作用域声明(预解释的时候)的变量是全局变量。
2)只有函数执行的时才会产生私有的作用域,比如for(){},if(){},switch(){}都不会产生私有作用域。
3)在私有作用域中声明的变量以及函数的形参都是私有变量。在私有作用域中,代码执行的时候遇到了一个变量,首先先要确定它是私有变量还是全局变量,如果是私有变量,那么就和外面没有关系,如果不是,就不断的向上级作用域进行查找,直至找到位置。这个过程的作用域连成的树形结构就叫作用域链。
//=>变量提升:var a;var b;var c;test=AAAFFF111;
var a=10,b=11,c=12;
function test(a){
//=>私有作用域:a=10 var b;
a=1;//=>私有变量a=1
var b=2;//=>私有变量b=2
c=3;//=>全局变量c=3
}
test(10);
console.log(a);//10
console.log(b)://11
console.log(c);//3
判断是否是私有变量一个标准就是是否是在函数中 var 声明的变量和函数的形参都是私有的变量
2、闭包
闭包是一种机制,函数执行时形成了一个新的私有的作用域保护了里面的私有变量不受外界的干扰(外面修改不了里面的,里面的修改不了外面的)。
这个因为函数执行的时候,首先会形成一个新的作用域,然后执行下面的步骤
1)如果有形参,先给形参赋值
2)进行私有作用域的预解释
3)私有作用域中的代码从上至下执行
var total=0;
function fn(num1,num2){
console.log(total);//->undefined 外面修改不了私有的(因为此时相等于var total ;console.log(total);所以在这里默认了重新声明了total的这个变量,也就是变量提升,但是有没有定义,所以值是undefined!)
var total=num1 +num2;
console.log(total);//->300
}
fn(100,200);
console.log(total);//->0 私有的也修改不了外面的
3、js中内存的分类
1)栈内存:用来提供一个js代码执行的环境,也就是作用域(全局作用域/私有作用域);
2)堆内存:用来存储引用数据类型(对象类型)的值。对象存储的是属性名和属性值,函数存储的是代码字符串。
三、var num = 12;和num = 12 有什么区别。
//例题1
console.log(num);//->undefined
var num=12;
//例题2
console.log(num2);//->Uncaught ReferenceError:num2 is not defined
num2=12;//不能预解释
var num = 12;这个语句,js会把它看作是两个声明:var num;num=12;第一个语句在预解释阶段执行,第二个语句在执行阶段执行。
最大的区别:带var的可以进行预解释,所以赋值之前的执行不会报错。不带var 的是不会进行预解释的,在语句之前执行会报错。
除此之外,num2=12;相当于给 window 增加了一个叫做 num2 的属性名,属性值是 12
而 var num=12;首先它相当于给全局作用域增加了一个全局变量 num,它也相当于给 window 增加了一个属性名 num2,属性值是 12。
//例题1
var total=0;
function fn(){
console.log("000"+total);//undefined
var total=100;
}
fn();
console.log(total);//0
//例题2
var total=0;
function fn(){
console.log(total);//0此时因为没有在函数的私有作用域里面声明变量,所以不会有变量提升,所以此时的total使用的是外部的全局变量total。
total=100;
}
fn();
console.log(total);//100
例题1的代码相当于
var total=0;
function fn(){
var total;
console.log("000"+total);//undefined
total=100;
}
fn();
console.log(total);//0
四、预解释的几种情况
1、预解释的时候不管你的条件是否成立,都会把带var的进行提前的声明
if(!("num" in window)){
var num=12;//这句话会被提到大括号之外的全局作用域:var num;->window.num;
}
console.log(num);//undefined
2、预解释的时候值解释等号“=”左边的,右边的值不参与预解释。
fn();//报错
var fn=function (){ //window下的预解释:var fn;所以此时是一个变量,不是一个函数
console.log("ok");
};
3、自执行函数:定义和执行一起完成了
自执行函数定义的那个 function 在全局作用域下不进行预解释,当代码执行到这个位置的时候定义和执行一起完成了。常见有以下几种形式:
(function(num){})(10);
~function(num){}(10);
+function(num){}(10);
-function(num){}(10);
!function(num){}(10);
4、函数体重return下面的代码虽然不再执行了,但是需要进行预解释;
return后面跟着的的都是我们返回的值,所以不进行预解释。
function fn(){
//预解释:var num;
console.log(num);//->undefined
return function(){
};
var num=100;
}
5、函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复”声明的代码中)是函数会首先被提升,然后才是变量。在预解释的时候,如果名字已经声明过了,不需要从新的声明,但是需要重新的赋值;
//例题1
function a() {}
var a
console.log(typeof a)//'function'
//例题2
var c = 1
function c(c) {
console.log(c)
var c = 3
}
c(2)//Uncaught TypeError: c is not a function
当遇到存在函数声明和变量声明都会被提升的情况,函数声明优先级比较高,最后变量声明会被函数声明所覆盖,但是可以重新赋值,所以上个例子可以等价为
function c(c) {
console.log(c)
var c = 3
}
c = 1
c(2)
题目1
fn();
function fn(){console.log(1);};
fn();
var fn=10;
fn();
function fn(){console.log(2);};
fn();
一开始进行预解释
function fn(){console.log(1);};
function fn(){console.log(2);};
所以后面的覆盖了前面的
所以
fn();//2
fn();//2
fn=10;
fn();//fn is not a function
fn();//不执行,因为前面报错了
题目2
//例题4
alert(a);
a();
var a=3;
function a(){
alert(10)
}
alert(a);
a=6;
a()
一开始预解释
var a;
function a(){alert(10)};
然后执行语句
alert(a);//function a(){alert(10)};
a();//10
a=3;
alert(a);//3
a=6;
a()//a is not function
js引擎主要做了三件事情
1.语法分析:就是检查代码有什么语法错误,如果没有则执行二
2.预编译:就是在内存中开辟一些空间,存放一些变量和函数
3.解释执行:执行代码
预编译的过程主要如下
1.创建AO对象
2.寻找函数的形参和变量声明,将变量和形参名作为AO对象的属性名,值为undefined。
3.将形参和实参相统一,也就是将实参的值赋值给形参
4.寻找函数中的函数声明,将函数名作为AO对象的属性名,值为函数体
需要注意几点的是:
1.如果函数是以变量的形式进行定义的,这种情况将不能进行函数提升,而仅仅是进行变量提升,也就是说AO对象里面的a的值为undefined
let a = function(){}
2.如果遇到函数名与变量名相同的情况,根据上面的顺序,函数名会覆盖掉变量名的。
像下面的代码中
function test (a) {
console.log(a);
var a = 2;
console.log(a);
function a() {
console.log(3)
}
console.log(b);
function b() {
console.log(4)
}
console.log(b);
console.log(c);
var c = function () {
}
console.log(c);
}
test(1);
首先创建AO对象(active object,也是GO global object)
AO{
}
然后寻找函数的形参和变量的声明,并且添加到AO中
AO{
a:undefined,
c:undefined,
}
再者是将形参和实参相统一
AO{
a:1,
c:undefined
}
寻找函数声明
AO{
a:function () {
console.log(3)
},
c:undefined,
b:function(){ console.log(4)}
}
然后开始逐行执行
function test (a) {
console.log(a);//function(){console.log(3)}
var a = 2;
console.log(a);//此时重新给a赋值,所以此时输出2
function a() {//不执行
console.log(3)
}
console.log(b);//function(){console.log(4)}
function b() {
console.log(4)
}
console.log(b);//function(){console.log(4)}
console.log(c);//undefined
var c = function () {
}
console.log(c);//function(){}
}
函数执行完毕,销毁AO对象
除此之外,预编译发生在什么时候呢?
因为js是解释性语言,也就是编译一句,执行一句
所以预编译发生在函数执行的前一刻内。