吃透预解释,从此再也不用担心!

Posted 旁征博引&厚积薄发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了吃透预解释,从此再也不用担心!相关的知识,希望对你有一定的参考价值。

Author:李金涛 Form:光环国际 Time:2017-12-31 23:49(跨年夜的最后一刻,我在辛勤耕耘我的“预解释”,收获满满,甚喜!)

定义:预解释(变量提升)js在运行前,先把所有带varfunction关键字的提前声明或定义。且预解释是发生当前作用域下的。

1,全局预解释阶段:

1)全局作用域与全局变量:当浏览器加载html页面的时候,首先会提供一个供全局javascript代码执行的环境,称之为全局作用域(global/ window)window全局作用域下定义的变量-->全局变量,全局都可以调用和修改。在当前作用域中,js代码从上到下执行之前,浏览器会默认的 先把所有带varfunction关键字的进行提前的声明或者定义--->预解释(变量提升)

2var关键字的和带function关键字的在预解释的时是不一样的
①var:预解释的时候只提前的声明,在代码执行的过程中才定义赋值
②function:预解释的时候声明+定义都完成了(这也是为什么函数可以把调用写到定义前面的原因。function预解释的时候,会开辟的内存空间,(我们叫堆内存;)并且把function内的代码当做字符串储存在堆内存内

2,代码执行期间:

①顺序执行。对var的变量赋值;function(){}跳过(已经预解释过);

②若function() 调用,会开辟一个新的内存地址,这个新的空间内存叫栈内存,这时候的的作用域是私有作用域;此时该有三步:形参赋值(无形参跳过);当前作用域下预解释;执行函数。当函数执行完成后,私有作用域就会销毁。

3,局部预解释:

1)作用域链:如果在当前作用域下的变量没有预解释,则向它的上一级作用域中找,直到找到window为止,如果window下也没有定义,就会报错;

2)闭包:函数执行时私有作用域下也要进行预解释,把所有在当前私有作用域下带varfunction的进行提前的声明或者定义;如果是在私有作用域中预解释的了我们称之为私有变量或私有函数;函数中的私有的,函数外面不能直接使用的,我们把这种机制叫做闭包;

 

4,预解释是毫无节操的一种机制

预解释注意事项:

1)预解释只发生在在varfunction上。即只能对varfunction进行提前声明或定义,window.a这样的不会进行预解释;

2)预解释只对当前脚本块起作用,不能跨脚本。但是申明的全局变量,在下个脚本块可以使用。  

3在预解释重名只声明一次,不重声明,但是要重的定义。(在以后的项目中,切记不要把函数名和变量名相同)

4js只对等号左边预解释,等号右边是赋值,不会进行预解释(return 后面也是值)。

5)条件内部只声明不定义。不看条件,见到 var就声明,function也只声明。(条件成立时,顺序执行)

    也就是说,即使条件不成立,里面只要有varfunction也会预声明。

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>
<!--&lt;!&ndash;6.预解释只发生在同一个脚本块,不能跨脚本&ndash;&gt;-->
<!--<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);

    //答案是:undefinedundefined1312

</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);

    //答案是:undefined121312

</script>

得到的结果是完全不一样的;

再次修改:两个var都去掉;

<script>

    console.log(a);//此时是没有预解释过a的,a就是没有定义的;会报错; a is not undefined

    a=12;//也没有vara 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;//也没有vara 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();

    //答案:100fn 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>

 

以上是关于吃透预解释,从此再也不用担心!的主要内容,如果未能解决你的问题,请参考以下文章

PR视频缓存好烦啊,两分钟快速分析缓存原理,从此再也不用担心!

保姆级神器 Maven,再也不用担心项目构建搞崩了

从此再也不担心Nginx跨域设置了

再也不用担心了

有了这个插件,再也不用担心代码不合规范了

Idea代码自动补全,良心插件推荐:Codota,再也不用担心写不出代码了。