javascript系列 ————变量声明提升(一)

变量声明提升

既然提到变量的声明提升,那么只要搞清楚三个问题:

  1. 什么是变量
  2. 什么是变量声明
  3. 声明提前到什么时候

什么是变量?

变量包括两种:普通变量和函数变量。

* 普通变量:凡是用Var标识的都是普通变量。

比如:

var x=1;               
var object={};
var  getA=function(){}; 
 //以上三种均是普通变量,但是这三个等式都具有赋值操作。所以,要分清楚声明和赋值。声明是指 var x; 赋值是指 x=1; 

* 函数变量:函数变量特指的是下面这种,fun就是一个函数变量。

function fun(){} ;// 这是指函数变量. 函数变量一般也说成函数声明。

           * 类似下面这个不是函数声明,而是函数表达式

var getA=function(){}      //这是函数表达式
var getA=function fun(){}; //这也是函数表达式,不存在函数声明。关于函数声明和函数表达式的区别,详情见javascript系列---函数篇第二部分

 什么是变量声明?

变量有普通变量和函数变量,所以变量的声明就有普通变量声明和函数变量声明

 * 普通变量声明

var x=1;        //声明+赋值
var object={};  //声明+赋值

             上面的两个变量执行的时候总是这样的

var x = undefined;      //声明
var object = undefined; //声明
x = 1;                  //赋值
object = {};            //赋值

 关于声明和赋值,请注意,声明是在函数第一行代码执行之前就已经完成,而赋值是在函数执行时期才开始赋值。所以,声明总是存在于赋值之前。而且,普通变量的声明时期总是等于undefined.

 *  函数变量声明

函数变量声明指的是下面这样子:

function getA(){}; //函数声明

声明提前到什么时候?

所有变量的声明,在函数内部第一行代码开始执行的时候就已经完成。


在javascript中,函数及变量的声明都将被提升到作用域(函数)的最顶部。

Javascript代码执行分为两个大步:

1. 预解析的过程

 作用域是否已经有一个该名称的变量存在于同一个作用域的集合中,

          如果有会忽略声明,继续进行编译;

          如果没有会要求作用域在当前作用域的集合中声明一个新的变量。

2.代码的执行过程

          在当前作用域集合中是否存在一个叫作name的变量。如果是,就会使用这个变量;如果不是,会继续查找改变量。

 

在读取代码的过程中,就产生了将所有声明提升到顶端,然后再从上往下执行。由此产生了变量提升和函数提升。

  • 变量提升只会提升变量名的声明,而不会提升变量的赋值初始化
  • 同一个变量只会声明一次,其他的会被忽略掉。
  • 函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上。

遇到"<script >"标签的话 js 就会进行预解析,将变量 var 和 function 声明提升,但不会执行 function,然后就进入上下文执行,上下文执行还是执行预解析同样操作,直到没有 var 和 function,就开始执行上下文。

 例如:

a=5;
show();
var a;
function show(){};

执行预解析执行过程:

function show(){};
var a;
a=5;
show();

代码预解析变量声明提升程序在执行过程,会先将代码读取到内存中检查会将所有的声明在此时进行标记,所谓的标记就是让 js解析器知道有这个名字,后面再使用名字的时候不会出现未定义的错误。这个标记过程就是提升。 

  • 解说什么叫声明?

声明有两种:

1.名字的声明,或者标识符的声明(变量名声明 注意:JavaScript 只有声明的变量会提升,初始化的不会)

      * 名字的声明就是让解释器知道有这个名字

      * 名字没有任何数据与之对应

2.函数的声明

      * 函数声明包含两部分(函数声明(function foo(){})、匿名函数(函数表达式var foo=function(){}))

      * 函数声明与函数表达式有区别,函数声明是单独写在一个结构中,不存在任何语句,逻辑判断等结构。

      * 使用匿名函数的方式不存在函数提升,因为函数名称使用变量表示的,只存在变量提升

function f(){
    function func(){}//函数声明
    if(true){
        function func2()//函数表达式
         // 判断函数是不是在语句块中
     }
     var  f=function func3(){};//函数表达式
     this.sayHello =function(){};//函数表达式
     var i=1;
     function func4(){}//声明
     var j=2;

}

   * 首先函数声明告诉解析器有这个名字存在,该阶段与名字声明一样

   * 告诉解析器,这个名字对应的函数体是什么。

例子1:

var num=1;
function num(){
    alert(num);
}
num(); // 不是一个函数
console.log(num)//结果为1

 代码分析:

        1.预解析代码,提升名字

                * 首先提升名字num

                * 再提升函数名,但是名字已经存在,因此只做第二步,让名字与函数体对应上

                * 结论就是代码中已经有一个函数num了

        2.开始执行代码,第一句话从赋值语句开始执行

                * 给num赋值为1

                * 覆盖了函数

        3.调用num,由于num中存储的是数组1,因此报错(不是一个函数体)。

例子2:

var num = 123;
function foo(){
    console.log( num );    //undefined
    var num = 456;
    console.log( num );    //456
}
foo();

 代码分析:

         1.预解析代码,提升num 名字和foo函数;

         2.执行第一句话:num=123;

         3.执行函数调用

                * 函数调用进入函数的一瞬间也要进行预解析,此时解析的是变量名num

                * 在函数内部是一个独立的空间,允许使用外部的数据,但是现在num声明同名,即覆盖外面的

                * 执行第一句 打印num,没有数据,undefined

                * 执行第二句 赋值:num=456;

                * 执行第三句 打印num,结果456

另外一种情况:

var num = 123;
function foo(){
    console.log( num );    //123
    num = 456;
    console.log( num );    //456
}
foo();
console.log(num)//456

代码分析

         1.预解析代码,提升num和foo函数

         2.执行第一句话:num=123;

         3.执行函数调用

                * 函数调用进入函数的一瞬间也要进行预解析,此时解析的是变量名num

                * 在函数内部是一个独立的空间,允许使用外部的数据(但函数内没有var 声明num,变量名不会被提升,会直接访问的函数外面的num,即函数外面上一个变量)。

                * 执行第一句 打印num,全局数据,123

                * 执行第二句 赋值:num=456;

                * 执行第三句 打印num,结果456

                * 最后由于第二句num没有变量声明,此时的num直接覆盖了函数上一个元素的num ;并赋值 num=456;打印num,结果为456.

例子3:

if(!’a’ in window){
  var a=123;
}
console.log(a);

代码分析:

         1.首先,预解析,读取提升a,有一个名字a存在了

         2.其次,in运算符:判断某一个字符串描述的属性名是否在对象中

                * var o={name:"jim"}; "name" in o, "age" in o

                * 执行第一个判断:!“a" in window

                       * "a " in window结果为真

                       * !得到假

                * if 内部的赋值不执行

                * 最后,打印结果a 的值为 undefined

例子4:

if ( false ) {
    function f1 () {
        console.log( 'true' );
     }
} else {
    function f1 () {
        console.log( 'false' );
     }
}
f1();
//false(老浏览器)
//true(新浏览器)

代码分析:

         1. 预解析 :提升 f1函数,只保留提升后的内容,所以打印false

         2.执行代码,第一句就是一个空的if结构if(true){}else{}

         3.执行函数调用,得到false

3. 函数声明与函数表达式有区别

function foo(){}

var foo=function (){}

  1.  上面的语法是声明,可以提升,因此在函数上方也可以调用
  2. 下面的语法是函数表达式,函数名就是foo ,他会提升,提升的不是函数体。
  3. 函数表达式也是支持名字语法
Var foo=function func(){};
    func();//  func is not define
   

                *  函数有一个属性name,表示是函数名,只有带名字的函数定义,才会有name属性值,否则是”“

                *  但是,函数表达式的名字,只允许在函数内部使用,ie8可以访问

Var foo=function func(){};
    func();//  func is not define
    console.log(func)//同上
var foo=function func(){
     console.log(func)//打印函数体
};
foo();

                *  () 可以将函数转换成表达式

(function foo(){ alert(1123)});
 foo();
// func is not define

新的浏览器中,写在if、while、do..while结构中的函数,都会将函数的声明转换成特殊的函数表达式 
将代码

if(...){
   function foo(){..}
}

转换成

if (...) {
    var foo = function foo () { .... }
}

 4使用匿名函数的方式

声明:

var 变量名称=function(形参列表){

//函数体

}

 调用:

变量名(实参列表)

注意:使用匿名函数的方式不存在函数提升,因为函数名称使用变量表示的,只存在变量提升。例:

var foo =function() {
   console.log( 2 );
}
 function foo() {
    console.log( 1 );
};
foo();//结果为1

 为什么是2不是1。 由于foo()是一个变量,因此这个变量的声明也将提升到顶部,而变量的赋值依然保留在原来的位置。需要注意的是:(*注意:函数优先,虽然函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量。函数声明会连通命名和函数体一起被提升至作用域顶部。

//函数、变量声明提升后
function foo(){         //函数声明提升到顶部
    console.log(1);
}

var foo;                //变量声明提升
foo=function(){         //变量赋值依然保留在原来的位置
    console.log(2);
}
foo();                   //最终输出2

参考

猜你喜欢

转载自blog.csdn.net/qq_27628085/article/details/84628181