阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      JavaScript 预解释的原理

      预解释的原理

      • 预解释的不同机制
      • var的预解释机制
      • function的预解释机制
      • 预解释机制
      • in的用法
      • 面试题练习

      预解释的的不同机制

      预解释也叫预声明,是提前解释声明的意思;预解释是针对变量和函数来说的;但是变量和function的的预解释是两套不同的机制;

      • 当浏览器加载我们的HTML页面的时候,首先会提供一个供JS代码执行的环境->全局作用域global(浏览器中的全局作用域,也叫顶级作用域是window)
      • JS中的内存空间分为两种:栈内存、堆内存
        • 栈内存;提供JS代码执行的环境,存储基本数据类型的值;->全局作用域或者私有的作用域其实都是栈内存;
        • 堆内存;存储引用数据类型的值(对象是把属性名和属性值储存进去,函数是把函数体内的代码当做字符串储存进去)
      • 在当前的作用域中,JS代码执行之前,浏览器首先会默认的把所有代var和function的进行提前的声明或者定义,->“预解释”(也叫变量提升)

      var的预解释机制

      var a=1

      • 1、代码运行之前,先扫描有没有带var关键字的变量名,有的话,为这个变量名,在内存里开一个空间;这时候变量名a是不代表任何值的;用undefined来表示;undefined是一个标识符/记号,表示找不到这个变量名所代表的数据;不存在的意思;这个阶段叫变量的声明;

      • 2、当代码运行的时候,则给数据1开辟一个内存空间;

      • 3、让数据1和变量名a绑定在一起;变量类型指的就是数据类型;按照js语言的原理来说变量类型有undefined类型;但是数据类型是没有undefined这种数据类型的;只有"undecided"这种字符串类型(字符串类型是数据类型的一种);同理也没有unll这个数据类型,但是有"null"这种字符串类型;

               var num;
              //1、声明(declare):var num; ->告诉浏览器在当前作用域中有一个num的变量了,如果一个变量只是声明了但是没有赋值,默认的值是undefined
            console.log(num);//->undefined
        	
              num = 12;
              //2、定义(defined):num=12; ->给我们的变量进行赋值
            console.log(num);//->12
        
              //变量提前使用的话,就是undefined
              console.log(testStr);//undefined
              var testStr="22222222"
        

      function关键字的预解释步骤

      function fn(){……}

      在代码执行之前,把所有的带function关键字的脚本都扫描一遍,然后定义变量;并且同时给变量赋值;

      • 1、函数的定义只是保存一些字符串;预解释的时候在内存里保存fn大括号里面的字符串;

      • 2、代码运行时候,读到fn()时候,这个时候就是函数的运行;函数的运行,会先开辟一个堆内存把字符串当做代码在堆内存中再次运行,函数产生的作用域内还会再进行预解释和代码运行;函数如果多次执行;会产生多个作用域;但是产生的多个作用域里面的内容都是相互独立的;互相没有关系;(在原型和原型链时候再仔细研究原理;)

              fn(100,200);//->可以在上面执行,因为预解释的时候声明+定义就已经完成了
              function fn(num1, num2) {
                  var total = num1 + num2;
                  console.log(total);
              }
        

      总结

      • 1、var和function关键字的在预解释的时候操作还是不一样的
        • var -> 在预解释的时候只是提前的声明了这个变量,只有当代码执行的时候才会完成赋值操作
        • function -> 在预解释的时候会提前的把声明加定义都完成了(在代码执行的时候遇到定义的代码直接的跳过)
      • 2、预解释只发生在当前的作用域下,例如:开始只对window下的进行预解释,只有函数执行的时候才会对函数中的进行预解释;

      [重要]刚开始只对window下的进行预解释,fn函数中目前存储的都是字符串,所以var total没啥实际的意义,所以不进行预解释 -> “预解释是发生在当前作用域下的”

        综合题;

              console.log(obj);//->undefined
              var obj = {name: "朱安邦", age: 25};
              function fn(num1, num2) {//代码执行到这一行的时候直接的跳过这一块的代码,因为在预解释的时候我们已经完成了声明加定义
                  var total = num1 + num2;
                  console.log(total);
              }
              var num1 = 12;
              fn(num1, 100);//执行fn,把全局变量num1的值赋值给形参num1,把100赋值给形参num2
      
      • 下面是一个预解释思路

              var a,
                  b = 0,
                  fn = function () {
                      var a = b = 2;
                  };
              fn();
              console.log(a, b);
        

      把上面解析成下面就好理解了

              var a;
              window.b = 0;
              window.fn = function () {
                  //var a = b = 2;
                  var a = 2;//a是私有的和全局没关系
                  b = 2;//b是全局的
              };
              fn();//window.fn()
              console.log(a, b);//undefined 2
      

      预解释机制

      • 1、不管条件是否成立都要进行预解释

              console.log(a);//->undefined
              if (!!("a" in window)) {//"a" in window -> true
                  var a = "朱安邦";
              }
              console.log(a);//->朱安邦
        

        例子中的if是不成立的,预解释的时候,碰到非functon内的var,都会声明,无论你写在if else 还是别的判断里;结舌if 假设if语句起作用的话,那么第一次log a的时候,就会报错了(没有声明的变量,是不能直接用的,除非typeof),而声明并且没有赋值的表现才是undefined;假设不成立; 最开始总结的预解释步骤:代码运行之前,先扫描有没有带var关键字的变量名,有的话,为这个变量名,在内存里开一个空间;,预解释是发生在代码执行前的,所以if根本阻挡不了预解释;

      • 2、预解释只发生在"=“的左边,只把左边的进行预解释,右边的是值是不进行预解释的

      匿名函数之函数表达式:把函数定义的部分当做值赋值给一个变量或者元素的事件

              fn1();//->undefined() Uncaught TypeError: fn is not a function JS中只有函数可以执行 && JS上面的代码如果报错了,在不进行任何的特殊处理情况下我们下面的代码都不在执行了
            var fn1 = function () {
                console.log("ok");
            };
            fn1();
      	
            //预解释的时候:fn=xxxfff000
            fn2();//->"ok"
            function fn2() {
                console.log("ok");
            }
            fn2();//->"ok"
      

      预解释的时候:var fn; ->fn的默认值是undefined;这里即使有functon,也是不能进行进行预解释的

      • 3、函数体中return下面的代码都不在执行了,但是下面的代码需要参加预解释;而return后面的东西是需要处理的,但是由于它是当做一个值返回的,所以不进行预解释;

            var total = 300;
            function fn() {
              console.log(total);
              return function sum() {};//return是把函数中的值返回到函数的外面,这里是把function对应的内存地址返回的到函数的外面,例如:return xxxfff111;函数体中return下面的代码都不在执行了
              var total = 10;
                console.log(total);
            }
            fn();// undefined
        
      • 4、匿名函数的function在全局作用域下是不进行预解释的;

      匿名函数之自执行函数:定义和执行一起完成了;函数内的声明,只是在函数内使用;

              (function(num){
                var testStr="test"+num;
                console.log(num);
              })(100);
              console.log(testStr);// testStr is not defined
      
      • 5、在预解释的时候,如果遇到名字重复了,只声明一次,不重复的声明,但是赋值还是要重复的进行的

        预解释:

          var fn; 声明
          fn = xxxfff000; [声明]不要了+定义
          fn = xxxfff111; [声明]不要了+定义
          ->fn=xxxfff111
          var fn = 12;//window.fn=12
          function fn() {//window.fn=function(){}
          }
          function fn() {
          }
      

        window下的预解释:

          fn=xxxfff111; 声明+定义
          var fn; 声明 (这一步不需要处理)
          fn=xxxfff222; 声明+定义(只需要执行定义即可)
          ->fn=xxxfff222
      

      一个案例;

      fn();//->2
      function fn() {
          console.log(1);
      }
      fn();//->2
      var fn = 13;//var fn;跳过  fn=13(在预解释的时候没有执行这句话赋值的操作)
      fn();//->13() Error:fn is not a function
      function fn() {
          console.log(2);
      }
      fn();
      

      JS中作用域只有两种:

      • window全局作用域、
      • 函数执行形成的私有作用域;
      • {name:""} if(){} for(){} while(){} switch(){} 这些都不会产生作用域;

      ES6可以用let形成块级作用域;http://www.cnblogs.com/snandy/archive/2015/05/10/4485832.html

      【in】:检测某一个属性名是否属于这个对象(不管是私有的属性还是公有的属性,只要是当前对象的属性返回true,不是的话返回false)

      var obj = {name: “王小波”, age: 30}; console.log(“name” in obj);//->true console.log(“height” in obj);//->false

      面试题练习

      1.写出代码运行的结果;并且把代码运行的步骤写出来;为什么得到所写的结果?

      var num = 20;
      var obj = {
        num: 30,
        fn: (function (num) {
          this.num *= 3;
          num += 15;
          var num = 45;
          return function () {
            this.num *= 4;
            num += 20;
            console.log(num);
          }
        })(num)//->把全局变量num的值20赋值给了自执行函数的形参,而不是obj下的30,如果想是obj下的30,我们需要写obj.num
      };
      var fn = obj.fn;
      fn();//->65
      obj.fn();//->85
      console.log(window.num, obj.num);//->240、120
      

      2.写出代码运行步骤

      function fo(){
        var i=0;
        return function (n) {
          return n+i++;
        }
      };
      var f=fo();
      var a=f(15);
      var b=fo()(15);
      var c=fo()(20);
      var d=f(20);
      console.log(a,b,c,d);//15 15 20 21
      
      1. 写出输出的结果

           var ary = [1,2,3,4];
           function fn(ary){
             ary[0] = 0;
             ary = [0];
             ary[0] = 100;
             return ary;
           }
           var res = fn(ary);
           console.log(ary);
           console.log(res);
        

      解析步骤如下;

            var ary = [1,2,3,4];
            function fn(ary){
              ary[0] = 0;//ary=[0,2,3,4] 这个时候修改了变量ary[0]了;ary[0] = 0;这个没有重写变量ary;参数ary只是代表了变量ary,保存的还是变量ary的地址;参数ary[0]指向的还是变量ary[0],也就是1,只是把1改为了0;
              ary = [0];//ary=[0],这里是重写参数ary了,等号右边的数据[0]保存在fn执行时候产生的堆内存中,参数ary指向的地址,由原来的变量ary地址,重写指向到数据[0]的保存地址,此时的参数ary已经与原来ary无关系了;两者保存的分别是不同的内存地址;
              ary[0] = 100;//ary=[100]
              return ary;//[100]
            }
            var res = fn(ary);
            console.log(ary);// [0,2,3,4]
            console.log(res);//[100]
      

      图解原理如下;

      目录
      目录