Author:李金涛 Form:光环国际 Time:2017-12-31 23:49(跨年夜的最后一刻,我在辛勤耕耘我的“预解释”,收获满满,甚喜!)
定义:预解释(变量提升):js在运行前,先把所有带var和function关键字的提前声明或定义。且预解释是发生当前作用域下的。
1,全局预解释阶段:
(1)全局作用域与全局变量:当浏览器加载html页面的时候,首先会提供一个供全局javascript代码执行的环境,称之为全局作用域(global/ window)。在window全局作用域下定义的变量-->全局变量,全局都可以调用和修改。在当前作用域中,js代码从上到下执行之前,浏览器会默认的 先把所有带var和function关键字的进行提前的声明或者定义--->预解释(变量提升)
(2)带var关键字的和带function关键字的在预解释的时是不一样的
①var:预解释的时候只提前的声明,在代码执行的过程中才定义赋值
②function:预解释的时候声明+定义都完成了(这也是为什么函数可以把调用写到定义前面的原因) 。function预解释的时候,会开辟的内存空间,(我们叫堆内存;)并且把function内的代码当做字符串储存在堆内存内
2,代码执行期间:
①顺序执行。对var的变量赋值;function(){}跳过(已经预解释过);
②若function() 调用,会开辟一个新的内存地址,这个新的空间内存叫栈内存,这时候的的作用域是私有作用域;此时该有三步:形参赋值(无形参跳过);当前作用域下预解释;执行函数。当函数执行完成后,私有作用域就会销毁。
3,局部预解释:
(1)作用域链:如果在当前作用域下的变量没有预解释,则向它的上一级作用域中找,直到找到window为止,如果window下也没有定义,就会报错;
(2)闭包:函数执行时私有作用域下也要进行预解释,把所有在当前私有作用域下带var和function的进行提前的声明或者定义;如果是在私有作用域中预解释的了我们称之为私有变量或私有函数;函数中的私有的,函数外面不能直接使用的,我们把这种机制叫做闭包;
4,预解释是毫无节操的一种机制。
预解释注意事项:
(1)预解释只发生在在var和function上。即只能对var和function进行提前声明或定义,window.a这样的不会进行预解释;
(2)预解释只对当前脚本块起作用,不能跨脚本。但是申明的全局变量,在下个脚本块可以使用。
(3)在预解释时,重名只声明一次,不重复声明,但是要重复的定义。(在以后的项目中,切记不要把函数名和变量名相同)
(4)js只对等号左边预解释,等号右边是赋值,不会进行预解释(return 后面也是值)。
(5)条件内部只声明不定义。不看条件,见到 var就声明,function也只声明。(条件成立时,顺序执行)
也就是说,即使条件不成立,里面只要有var或function也会预声明。
(6)预解释不受return影响,return下一行,会在当前作用域下预解释。
但return后的为返回值且不执行,相当于"="右边不预解释
(7)自执行函数不参与预解释。
下面是我的demo代码:
//预解释规律:
//预解释是毫无节操的一种机制;
//1,不看条件,见到 var就声明,function也只声明;
//2,预解释只管"="左边,"="右边是值 不参与预解释
//3,自执行函数不参与预解释
//4,return后的为返回值且不执行,相当于"="右边不预解释;但是return下一行,会在当前作用域下预解释。
//5,预解释,重名只会声明一次,但会不断赋新值;(JS很懒很精明,不会做重复的事)
//6.预解释只发生在同一个脚本块,不能跨脚本
// 7,预解释只发生在在var和function上。window.a这样的不会进行预解释;
// console.log(num);//undefined
// if(!("num" in window)){
// var num=10;
// }
// console.log(num);//undefined
// console.log(f1);//undefined
// if(!(f1 in window)){
// function f1() {//条件内部,函数只声明不定义
// console.log("条件内部函数体");
// }
// }
// console.log(f1);//undefined
// console.log(f1());// TypeError: f1 is not a function
// console.log(f1);//undefined
// if(1==1){
// function f1() {//条件内部,函数只声明不定义
// console.log("条件内部函数体");
// }
// }
// console.log(f1);//函数体
// console.log(f1());//条件内部函数体 undefined
// console.log(f12);//正常函数体
// console.log(f12());//正常函数体 undefined
// function f12() {
// console.log("正常函数体");//undefined
// }
// console.log(f12());//正常函数体 undefined
//
// function f11() {
// num1=1;
// console.log(num1);
// }
// console.log(f11);//函数体
// console.log(f11());//1 undefined
// console.log(a);//undefined
// if(1==1){
// var a=12;
// }
// console.log(a);//12
// console.log(a);//undefined
// if(1==2){
// var a=12;
// }
// console.log(a);//undefined
//2,预解释只管"="左边,"="右边是值 不参与预解释
//// console.log(num);//ReferenceError: num is not defined
// var f2=function () {
// var num=2;
// };
// console.log(num);//ReferenceError: num is not defined
// f21();//TypeError: f21 is not a function
// var f21=function () {
// console.log("ok");
// };
// f21();//ok 正常调用匿名函数
//3,自执行函数不参与预解释
// console.log(num);//num is not defined
// (function (num) {console.log(num+1)})(100);
// ~function (num) {console.log(num+1)}(100);
// (function (num) {console.log(num+1)})(100);
// console.log(num);//num is not defined;num是形参,是局部变量;
//4,return后的为返回值且不执行,相当于"="右边不预解释;但是return下一行,会在当前作用域下预解释。
// function f4() {
// console.log(num);//undefined
//// f41();//f41 is not defined
// f42();//okf42 f42函数已经预解释
// return function f41() {
// console.log("ok");
// };
// var num=4;
// function f42() {
// console.log("okf42");
// };
// console.log(num);//return后不执行,没有结果
// }
// f4();
// console.log(num);//num is not defined
//5,预解释,重名只会声明一次,但会不断赋新值;(JS很懒很精明,不会做重复的事)
// console.log(f5);//? f5() {console.log("52")}
// f5();//52
// function f5() {console.log("51")};
// f5();//52
// var f5=50;
// console.log(f5);//50
// f5();//52() TypeError: f5 is not a function
// function f5() {console.log("52")};
// f5();//报错后不在执行,JS是解释性语言,边读边解释,遇错即停止。
</script>
<!--<!–6.预解释只发生在同一个脚本块,不能跨脚本–>-->
<!--<script>-->
<!--console.log(a);//预解释只发生在同一个脚本快,不能夸脚本;//所以此时a is not defined-->
<!--</script>-->
<!--<script>-->
<!--var a=13;-->
<!--</script>-->
<script>
// var a=13;
// var a;
</script>
<script>
// console.log(a);//这个时候是弹出来13、以后写代码,不同代码块的顺序一定要注意;因为第一个脚本快已经赋值了
(代码块的值是可以用的。这时候已经过了预解释阶段了,是定义过的了)
</script>
<script>
// function f() {
// return
// }
// console.log(f);
// console.log(f());//undefined 没值跟没有return一样
// console.log(a);
// var a=10;
// console.log(a);
//window.a这样的不会进行预解释
// console.log(a);//a is not defined
// window.a=10;
// console.log(a);
</script>
下面是一个预解释面试题;
<script>
console.log(a);
var a=12;
function sum(){
console.log(a);
var a=13;
console.log(a);
}
sum();
console.log(a);
//答案是:undefined、undefined、13、12;
</script>
原理分析
————
预解释是毫无节操的一种机制;预解释完成;代码从上到下加载;分别给变量定义赋值(带var的情况;);如果遇到function sum(){}定义的这个部分,直接跳出即可;如果遇到的是函数的执行,则进行下面的处理;
开辟一个新的私有作用域(栈内存)、用来执行函数中的js代码;
在执行代码前 ,
思考:闭包内不带var的变量赋值,和全局情况下不带var的变量赋值是什么流程?
预解释是发生在当前作用域下的;
预解释的知识点:
如果判断他的上一级作用域;
看存储这个函数的堆内存在哪一个作用域(A)下,那么这个函数的执行的上一级作用域就是A;(有些时候函数A内套函数B,套函数C,而C可能是在全局作用域内拿出来运行的,这个他的作用域是谁??)
如何判断是私有的还是全局的;
在一个函数中只有预解释声明过的和形参是定义过的,是私有的;否则往他的上一级作用域找,如果上一级作用域也没有,则继续找;一直找到window为止;
题目修改:去掉闭包内的一个var;
<script>
console.log(a);
var a=12;
function sum(){
console.log(a);
a=13;//没有var;没有预解释过,也不是形参。所以不是私有变量的;要往上一层找,一直找到window为止;
console.log(a);
}
sum();
console.log(a);
//答案是:undefined、12、13、12;
</script>
得到的结果是完全不一样的;
再次修改:两个var都去掉;
<script>
console.log(a);//此时是没有预解释过a的,a就是没有定义的;会报错; a is not undefined
a=12;//也没有var。a is not defined;下面代码会都不执行了。前提是没有进行异常捕获处理;
function sum(){
console.log(a);
a=13;//没有var;没有预解释过,也不是形参。所以不是私有变量的;要往上一层找,一直找到window为止;
console.log(a);
}
sum();
console.log(a);
//答案是:Uncaught ReferenceError: a is not defined;;就是a is not defined;其它都没有了。
</script>
这时候就会报错了;
如果再再次修改:注释掉第一行;
//console.log(a);
答案是
<script>
//console.log(a);
a=12;//也没有var。a is not defined;下面代码会都不执行了。前提是没有进行异常捕获处理;
function sum(){
console.log(a);
a=13;//没有var;没有预解释过,也不是形参。所以不是私有变量的;要往上一层找,一直找到window为止;
console.log(a);
}
sum();
console.log(a);
//答案是:12/13/13
</script>
带var不带var的区别;
全局作用域下才有下面这种一个机制的;
var a=12;//在全局作用域下定义了一个变量a=12,相当于给window增加一个属性名a;属性值是12;
a=12//就是给window增加了一个属性名a,值是12;(严格模式下是会报错的;)
区别:上面的预解释了,下面是没有预解释;
再再在次修改是:
<script>
//console.log(a);
//a=12;
function sum(){
console.log(a);
a=13;
console.log(a);
}
sum();
console.log(a);
//答案是:Uncaught ReferenceError: a is not defined
</script>
再再再次修改:
<script>
//console.log(a);
//a=12;
function sum(){
//console.log(a);
a=13;//当前这种情况是属于给window增加一个属性值;属性值是13;
console.log(a);
}
sum();
console.log(a);
//答案是:13,13、
</script>
——————–
面试题思路;
<script>
var a=12;
function fn(){
var a=13;
function ff(){
a++;
console.log(a);
}
return ff;
}
var f=fn();
f();
console.log(a);
//答案:14/12。
// ff是存在fn中的;ff里面的a是私有的。向上级找,也就是fn里面;
</script>
虽然f()在外面执行,但是他的上级作用域还是fn(),a是要在上级作用域里找的;
————
预解释是毫无节操的一种机制;
<script>
if(!("a" in window)){
var a=12;
}
console.log(a);//结果是undefined,而不是报错。
</script>
1、预解释是发生在当前作用域下的;
2、如果有if判断;不管条件是否成立,都要预定义;
上面题目的分析;
规定:var a=12;相当于给window增加一个属性名,属性值是12;
in:用来检测某一个属性名是否是这个对象的;”a”in window 的意思是:判断window下有没有”a“这个属性名;属于的话返回true,不属于返回false;
预设值的时候;if判断里面的a是会被预解释的,相当于给window加一个属性名;
执行代码;if(!(“a” in window))。这个条件是false,所以不继续执行。
这个时候console.log(a)。这时候a已经被预解释了,只是a没有定义,是undefined、所以结果是undefined;(并非报错的那种a is undefined)【分析完成】
3、预解释值发生在=左边的,右边的不进行预解释;
<script>
function fn(){};//这个是function声明+定义的;
var fn=function(){}//fn预解释,只声明了fn的变量;
//虽然两个一样的用法;但是预解释不一样的;
</script>
第二个例子:
<script>
fn();// fn is not a function
var fn=function(){}
//fn();
</script>
如果改成下面,就不会报错;会执行fn()
<script>
//fn();// fn is not a function
var fn=function(){}
fn();
</script>
就是下面这样的;
<script>
//fn();// fn is not a function
var fn=function(){
console.log("123")
}
fn();
//控制台是显示123
</script>
接上面的继续总结;
4、只有预解释发生在同一个脚本快,不能夸脚本
<body>
<script>
console.log(a);//预解释只发生在同一个脚本快,不能夸脚本;//所以此时a is not defined
</script>
<script>
var a=13;
</script>
</body>
<body>
<script>
var a=13;
</script>
<script>
console.log(a);//这个时候是弹出来13、以后写代码,不同代码块的顺序一定要注意;因为第一个脚本快已经赋值了(代码块的值是可以用的。这时候已经过了预解释阶段了,是定义过的了)
</script>
</body>
5、自执行函数不进行预解释
;(function(){})();//自执行函数不解析预解释
6、函数中,return后面的代码也要进行预解释;
7、在预解释的时候,如果发现名字重了,不重新的声明,但是要重新的定义;
function a(){
//变量的名a和函数名a,如果重名后也是冲突的;
}
</script>
下面是一个经典的思路,一个不错的面试题:
<script>
fn();
var fn=13;
fn();
function fn(){
console.log(100);
}
fn();
//答案:100、 fn is not a function(只有函数才能执行;fn()相当于13(),这个是不能执行的;)、后面的不执行了
</script>
留意:报错的fn is not a function也有浏览器弹出 number is not function
练习做题;
<script>
console.log(fn);
function fn(){
console.log(1);
}
console.log(fn);
var fn;
console.log(fn);
function fn(){
console.log(2);
}
console.log(fn);
var fn=10;
console.log(fn);
</script>