最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 读懂JS核心(二)--变量提升与函数声明提升

    正文概述 掘金(何以庆余年)   2021-05-11   381

    变量提升与函数声明提升

    在上一节中,我们详细介绍了JS中的执行栈和执行上下文,还简单解释了出现变量提升的原因。这一节,我们会对变量声明、函数声明做详细的介绍,深入地了解变量提升和函数声明提升,最后我们还会介绍一下class的声明,以及let、const是如何工作的。

    首先,我们来看一个问题:

    console.log(a);
    function a() {
        console.log('fa')
    }
    
    console.log(b);
    var b = 'b';
    

    上述代码执行后会有怎样的打印结果呢?

    答案揭晓: ƒ a() { console.log('fa') }undefined

    为什么会有这样的结果呢?我们首先需要了解一下JavaScript中的预处理机制。

    预处理机制

    JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、class、const 和 let 这些语句,以确定其中变量的意义。

    var声明--变量提升

    var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。

    // 示例
    console.log(b);
    var b = 'b';
    

    在上述代码中,JavaScript 执行前会先对var b = 'b'做预处理,在全局环境声明了一个值为undefinedb变量,所以在第一行的console.log(b)时,会打印出undefined

    立即执行的函数表达式(IIFE

    因为早年 JavaScript 没有 let 和 const,只能用 var,又因为 var 除了脚本和函数体都会穿透,人民群众发明了“立即执行的函数表达式(IIFE)”这一用法,用来产生作用域。

    // 为文档添加了 20 个 div 元素,并且绑定了点击事件,打印它们的序号
    for(var i = 0; i < 20; i ++) {
        void function(i){
            var div = document.createElement("div");
            div.innerHTML = i;
            div.onclick = function(){
                console.log(i);
            }
            document.body.appendChild(div);
        }(i);
    }
    

    我们通过 IIFE 在循环内构造了作用域,每次循环都产生一个新的环境记录,这样,每个 div 都能访问到环境中的 i。

    如果我们不用 IIFE:

    
    for(var i = 0; i < 20; i ++) {
        var div = document.createElement("div");
        div.innerHTML = i;
        div.onclick = function(){
            console.log(i);
        }
        document.body.appendChild(div);
    }
    

    这段代码的结果将会是点每个 div 都打印 20,因为全局只有一个 i,执行完循环后,i 变成了 20。

    有了let关键词之后,可以用let来声明块级作用域,于是我们就可以不用IIFE了。

    function声明--函数声明提升

    在全局(脚本、模块和函数体),function 声明表现跟 var 相似,不同之处在于,function 声明不但在作用域中加入变量,还会给它赋值。

    // 示例
    console.log(a);
    function a() {
        console.log('fa')
    }
    

    在上述代码中,JavaScript 执行前会先对function a (){...} 做预处理,在全局环境声明了一个值为ƒ a() { console.log('fa') }a变量,所以在第一行的console.log(a)时,会打印出ƒ a() { console.log('fa') }

    注意:当function 声明出现在 if 等语句中的情况有点复杂,它仍然作用于脚本、模块和函数体级别,在预处理阶段,仍然会产生变量,但它不再被提前赋值:

    // 示例
    console.log(foo);
    if(true) {
        function foo(){
    
        }
    }
    

    这段代码得到 undefined,如果没有函数声明,则会抛出错误。这说明 function 在预处理阶段仍然发生了作用,在作用域中产生了变量,没有产生赋值,赋值行为发生在了执行阶段。出现在 if 等语句中的 function,在 if 创建的作用域中仍然会被提前,产生赋值效果。

    console.log(foo);
    if(true) {
        console.log(foo);
        function foo(){
    
        }
    }
    

    这段代码得到 undefinedƒ foo(){}

    class声明

    class声明在全局的行为与function、var都不一致,在class声明前使用class名,会抛出错误。

    // 示例
    console.log(C);
    class C {
    }
    

    上述代码会抛出异常Uncaught ReferenceError: c is not defined,这个行为很像是class没有预处理,但事实上并非如此。

    class 声明也是会被预处理的,它会在作用域中创建变量,并且要求访问它时抛出错误,class 的声明作用不会穿透 if 等语句结构,所以只有写在全局环境才会有声明作用。

    // 示例
    const a = 2;
    if(true){
        console.log(a); //抛错
        class a {
    
        }
    }
    

    这段代码在全局声明了一个值为2的a变量,但是在函数块中,又进行了一次class声明。class声明被预处理,在if的作用域中不会再访问外部声明的a变量,而是访问class声明的a类,所以抛出错误。

    let、const

    let 和 const 是都是变量的声明,它们的特性非常相似,与var声明有着很大的差异。

    let 和 const 声明虽然看上去是执行到了才会生效,但是实际上,它们还是会被预处理。如果当前作用域内有声明,就无法访问到外部的变量。

    备注:let、const 与 class的声明方式类似

    //示例
    const a = 2;
    if(true){
        console.log(a); //抛错
        const a = 1;   
    }
    

    在if的作用域中,const声明被预处理,JS引擎就已经知道后面的代码将会声明变量a,从而不允许我们访问外层作用域中的变量a。

    私货课堂

    先提出一个小问题:如果在同一个作用域中,同时存在函数声明和变量声明,他们的优先级是什么样子的?

    console.log(a);
    var a = 'varA';
    console.log(a);
    function a() {
        console.log('funA');
    }
    a();
    

    上述代码会依次输出ƒ a() { console.log('funA'); }varA、抛出异常Uncaught TypeError: a is not a function

    这是因为函数声明会优先于var变量声明,在第一行中,读取的a是函数声明(函数声明时会提前赋值),所以会打印ƒ a() { console.log('funA'); };继续运行到第二行时,因为已经声明过a了,所以会将varA赋值给变量a,这时a的值为varA。所以第三行会打印varA;继续运行到4~6行时,因为已经声明执行过function a(){}语句,所以跳过;继续执行到最后一行时,此时的a是字符串varA,而非函数,所以会抛出异常:Uncaught TypeError: a is not a function

    在同一个作用域中,如果同时存在函数声明和变量声明,只需要记住两句话即可解决问题:

    • 函数声明会优先于var变量声明
    • 同一作用域下存在多个同名函数声明,后面的会替换前面的函数声明

    放几道有意思的题目,供大家更好地理解:

    //Example1
    foo;
    var foo = function () {
        console.log('foo1');
    }
    
    foo();
    
    var foo = function () {
        console.log('foo2');
    }
    
    foo();
    
    //Example2
    foo();
    function foo() {
        console.log('foo1');
    }
    
    foo();
    
    function foo() {
        console.log('foo2');
    }
    
    foo();
    
    //Example3
    foo();
    var foo = function() {
        console.log('foo1');
    }
    
    foo();
    
    function foo() {
        console.log('foo2');
    }
    
    foo();
    

    总结

    在这一节,我们详细介绍了JS中变量声明提升、函数声明提升,还补充了class声明和let/const的声明方式。接下来我们会继续介绍其他JS核心知识。

    我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!

    如有问题,欢迎在留言区一起讨论。


    起源地下载网 » 读懂JS核心(二)--变量提升与函数声明提升

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元