• jacascript 函数声明、函数表达式与声明提升(hoisting机制)


    前言:这是笔者学习之后自己的理解与整理。如果有错误或者疑问的地方,请大家指正,我会持续更新!

    声明、定义、初始化

      声明的意思是宣称一个变量名的存在,定义则为这个变量分配存储空间,初始化则是给该变量名的存储空间赋予初始值;

      javascript 中,变量没有固定类型,其存储空间会随着初始化(并赋值)而变化;

            <script type="text/javascript">
                var a;//声明a
                console.log(a);//undefined;   只声明,没有赋值,返回undefined
                
                a = 'hello world!';//(定义)初始化变量a并且赋值'hello world!'
                console.log(a);//hello world!
                
                var a;//再次声明a,但没有赋值,a的值不会被清空
                console.log(a);//hello world!
            </script>

    hoisting 机制

      javascript 代码在执行时,一般是一行一行往下执行的,但是这里面又有一个变量声明提升的概念;

      javascript 引擎在执行时,会把所有变量声明和函数声明都提升到当前作用域的最前面(hoisting 机制);

    变量声明提升

            <script type="text/javascript">
    //            console.log(a);//a is not defined   a没有声明
                console.log(b);//undefined   没找到b(没有初始化赋值),但已经声明了
                var b = 'hello world!';   //声明并初始化b赋值'hello world!'
                console.log(b);//hello world!
            </script>

      上面代码中 a 和 b 的区别就是b声明提前了,a 没有声明;所以上面代码相当于:

            <script type="text/javascript">
                var b;//声明b
    //            console.log(a);//a is not defined  a没有声明
                console.log(b);//undefined
                b = 'hello world!';  //初始化并赋值'hello world!'
                console.log(b);//hello world!
            </script>    

    函数声明提升

      一直对一些基本的概念的理解有些模糊,先梳理以下:

    1. 函数声明(function statement),使用function关键字声明一个函数,再指定一个函数名,叫函数声明。如:function fnName(){};
    2. 匿名函数anonymous functions),使用function关键字声明一个函数,但是没有命名,这个函数就叫匿名函数,写法就是:function(){};
    3. 函数表达式(function expression),把一个匿名函数当做值传给一个变量,叫函数表达式,这是最常见的函数表达式语法形式。如:var fnName = function(){};

    函数声明

      函数声明(function statement),使用function关键字声明一个函数,再指定一个函数名,叫函数声明。如:function fnName(){};

      但需要注意的是:

    1. 函数的声明可以是有条件的,比如嵌套在 if 语句中,有的浏览器会将这种有条件的声明看成无条件的,不论条件是true还是false,我们最好不要这样写;
    2. 在严格模式下,只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。

      非严格模式下:

            <script type="text/javascript">
                //非严格模式
                if(true){
                    function find(){
                        console.log(456);
                    }
                }
                find();//456
                
                if(false){
                    function cantFind(){
                        console.log(123);
                    }
                }
                cantFind();//TypeError: cantFind is not a function
            </script>

      严格模式下:

            <script type="text/javascript">
                'use strict' //严格模式下
                if(true){
                    function find(){
                        console.log(456);
                    }
                }
                find();//ReferenceError: cantFind is not defined
            </script>

    函数表达式

      函数表达式(function expression),把一个匿名函数当做值传给一个变量,叫函数表达式,这是最常见的函数表达式语法形式。如:var fnName = function(){};

    1. 在 function 前面加!、+、 -、=甚至是逗号等或者把函数用()包起来都可以将函数声明转换成函数表达式;
    2. 但是!、+、-等运算符还会和函数的返回值进行运算,有时造成不必要的麻烦,所以我们一般用()把函数声明包起来或者用 = ,达到转换函数表达式的目的;
            <script type="text/javascript">
                //在function前面加!、+、 -、=甚至是逗号等到都可以将函数声明转换成函数表达式,我们一般用()或者 = 
                var a = function b(){  
                    return b; 
                }
                console.log(a());//function b(){console.log(b);}
                console.log(b());//ReferenceError: b is not defined   
                                 //函数表达式的标识符在外部作用域是看不到的,只能在内部作用域看到
            </script>

      函数声明提升有两种情况:1.函数声明;2.把函数作为一个值传给一个变量(函数表达式);

      和变量声明提升一样,函数声明也会提升,并且定义也会提升(这个说法也不太准确,看下面的例子)

            <script type="text/javascript">
                play();//函数声明提升了,之所以可以正常运行,是因为定义也被提前了 
                function play(){
                    console.log('hello world!');//hello world!
                }
            </script>

      以上代码中函数play()声明提升了,之所以可以正常运行,是因为函数声明提升了,定义也被提升了(这个说法也不太准确,看下面的例子),以上代码相当于:

            <script type="text/javascript">
                function play(){
                    console.log('hello world!');//hello world!
                }        
                play();
            </script>

      我们有时候会把一个函数作为值传给一个变量,这时变量会被声明提升,但函数不会,它只是一个函数表达式,我们只需要它的返回值给变量;

      如果它是一个具名函数表达式,那么函数表达式的标识符(函数名)在外部作用域是找不到的,只在内部作用于能找到;

            <script type="text/javascript">
                //console.log(play);//ReferenceError: play is not defined   声明错误
                //play();//ReferenceError: play is not defined 声明错误
    
                console.log(games);//undefined  变量声明了,但没赋值,所以没找到
                //games();//TypeError: games is not a function   类型错误
                
                var games = function play(){  //函数表达式
                    console.log(typeof play);
                }
                //play();//ReferenceError: play is not defined 声明错误
                games();//function  函数表达式的标识符(函数名)在外部作用域是找不到的,只在内部作用于能找到;
            </script>

      上面写的函数声明的定义也被提升了,这个说法并不太准确,

    <script type="text/javascript">
        if (!("a" in window)) {   
            function a() { 
                return '为什么下面弹出的a是undefined';
            }
        }
        console.log(a);  //undefined
        //如果定义也被提前,这里应该是function a(){},所以这里定义并没有被提前,只有函数名被提前了,然后if语句进不去了,
        //因为函数声明可以和函数表达式相互转换,只是写法不同而已,
        //所以上面的代码有个说法,这里的函数a可以理解成var a = function(){},函数声明的方式提前的是函数名,然而这又不能解释为什么函数能在函数声明之前能被调用;
        
        b();
        function b(){
            console.log('函数声明的疑问,为什么b能执行');
        }
    </script>

      因为函数声明可以和函数表达式相互转换,只是写法不同而已,所以上面的代码有个说法,函数a可以理解成 var a = function(){},函数声明的方式提前的是函数名,然而这又不能解释为什么函数能在函数声明之前能被调用;

    变量名解析顺序

      同一作用域下,相同变量名,后面的赋值会覆盖前面的;

      同一作用域下,如果变量名和函数名相同,那么函数名会覆盖变量名,不论它们在代码的顺序如何;但是它们的初始化赋值是按顺序执行的;

      所以,同一作用域下应避免使用相同变量名;

            <script type="text/javascript">
                console.log(a);//function a(){console.log('我是函数');}  函数名和变量名相同,函数名会覆盖变量名
                
                var a = 'hello world!';
                function a(){
                    console.log('我是函数');
                };
                
                console.log(a);//hello world!
                a();//TypeError: a is not a function  类型错误
            </script>

      上面代码中,a的值最后是字符串,原因就在于:

    1. 变量的赋值是按顺序执行的;
    2. 函数声明提升会把表达式也提前;
    3. 相同变量名,后面的赋值会覆盖前面的;

      上面的代码相当于:

            <script type="text/javascript">
                console.log(a);//function a(){console.log('我是函数');}  函数名和变量名相同,函数名会覆盖变量名
                
                function a(){
                    console.log('我是函数');
                };
                var a = 'hello world!';
                
                console.log(a);//hello world!
                a();//TypeError: a is not a function  类型错误
            </script>

    有一道经典面试题:

            <script type="text/javascript">
                b = c;
                console.log(b); 
                console.log(c);  
                b();
                console.log(a);    
                function c() {
                    a = 1, b = 2, c = 3; 
                };
            </script>

      函数c还没运行,所以里面的变量是不知道的,但是函数c已经声明提前了,又因为第一行b=c,所以b和c都是函数c ;

      函数c运行之后,里面的变量a,b,c被发现是全局变量,并且给它们赋值,所以外部能找到a=1,并且你还会发现b=2; c=3;

      如果函数c不曾运行过,那么是找不到它们的 ;

    这道题换一下:

            <script type="text/javascript">
                b = function c() {
                    a = 1, b = 2, c = 3;
                };
                
                b();
                console.log(a); 
                console.log(b); 
                console.log(c); 
            </script>

      函数 b 运行之后,发现了里面的全局变量,并且给它们赋值 a=1; b=2;

      但是有个特殊的变量 c,c 是函数表达式的名字,函数的名字,规范说明函数名不能被改变,所以这里设置 c=3 其实是对函数名赋值,是无效的,也不会被当做全局变量;而且函数表达式的标识符只有内部作用域可以找到,所以外部是找不到的,报错 ReferenceError: c is not defined 引用错误;

  • 相关阅读:
    电源锁
    Android的三种网络联接方式
    用tcpdump在手机上抓包
    图片出现波纹的问题
    Android 3.1以后 广播接收器的新机制
    OpenGL坐标
    用Messager进行IPC
    PHP 介绍
    View坐标,MotionEvent坐标, 二者的转换,可视区域
    OpenGL ES
  • 原文地址:https://www.cnblogs.com/sspeng/p/6611072.html
Copyright © 2020-2023  润新知