• let const var 比较说明


    现在先来做两道练习题

    for(var i=0;i<10;i++){
        var a='a'
        let b = 'b'
    }
    console.log(a)
    console.log(b)
    
    for(var i=0;i<3;i++){
        setTimeout(function(){
            console.log(i)
        },1000)
    }
    
    function(){
        console.log(a)
        var a =1;
        function a(){}
        console.log(a)
    }
    

    理解js作用域

    作用域我自己的理解是变量在某个的范围内可访问,那这个范围就是这个变量的作用域
    在ES5中,js只有两种形式的作用域:全局作用域函数作用域

    • 全局作用域:变量在程序中任意地方都可以访问到
    • 函数作用域:变量在函数内部可以访问到,在函数外部无法访问
    for(var i=0;i<10;i++){
        var a='a'
        let b = 'b'
    }
    console.log(a) //'a'
    console.log(b) //'b' is not defined
    

    上述代码中,变量a为全局变量

    function test(){
        var a = 'a'
    }
    test()
    console.log(a) // 函数外部无法直接访问函数内部变量,报错
    

    上述代码中,变量a为局部变量,控制台打印报错信息a is not defined

    function test(){
        a = 'a'
    }
    test()
    console.log(a) //'a'
    

    函数内部未使用var关键字定义变量,此时a为全局变量

    小结

    • ES5中, js的作用域分为全局作用域和函数作用域
    • 函数内部可以访问函数外部的全局变量,函数外部却无法直接访问函数内部的局部变量
    • 未使用var关键字定义的变量是全局变量

    现在我们知道函数内部可以访问函数外部的全局变量,函数外部却无法直接访问函数内部的局部变量。但有的时候我们需要读取函数内部的局部变量,该怎么办呢?


    闭包

    举个例子:

    function f1(){
        var a = 1;
        function f2(){
            return a;
        }
        return f2;
    }
    console.log(f1()()) //1
    

    上述代码就创建了一个闭包,我们可以在f1函数外部访问到f1函数内部的值。
    我们看到上述代码有两个特点:

    • f1函数嵌套了f2函数
    • f1函数返回了f2函数

    闭包的用处:

    1.很多js流行框架都是使用匿名自执行函数来避免变量污染
    ;(function(){
        //todo
    })()
    
    2.缓存:闭包可以让变量的值始终保存在内存中,因此在使用时也要注意不要滥用闭包
    function f1(){
        var n=999;
        nAdd = function(){n+=1}
        function f2(){alert(n)}
        return f2;
    }
    var result = f1(); //注意只有f1的返回值被外部引用,才不会被回收
    result(); // 999
    nAdd();
    result(); //1000
    
    3.封装
    var person = function(){
        var name = ‘default’
        return {
            getName:function(){ return name},
            setName:function(newName){ name=newName}
        }
    }()
    

    既然ES5中有定义变量的方法,那为什么ES6中又定义了let,const关键字呢?


    js中的变量提升

    var定义变量存在变量提升:只提升声明语句,不提升赋值语句

    var foo = {n:1};
    (function(foo){
        console.log(foo.n);
        foo.n = 3;
        var foo = {n:2};
        console.log(foo.n);
    })(foo)
    console.log(foo.n); 
    

    执行上述代码,我们可以看到控制台中按顺序依次打印:1,2,3。这是因为Javascript先编译后执行。编译阶段,先声明变量,所以引擎会将上面的代码理解为以下格式

    var foo = {n:1};
    (function(foo){
        var foo;
        console.log(foo.n)
        foo.n = 3;
        foo = {n:2};
        console.log(foo.n)
    })(foo);
    console.log(foo.n)
    

    说明:

    1. 函数内部定义变量foo时,因为当前作用域中已经存在名为foo的变量,所以编译器忽略当前声明,继续进行编译,因此第一次打印的内容为外部变量foo的属性n值:1
    2. foo.n=3 改变的是外部变量foo,foo={n:2}将foo指向了内部变量,并重新赋值为{n:2},所以第二次打印的内容为内部重新赋值的变量foo的属性n值:2
    3. 第三次打印内容是外部变量foo.n,因为函数内容已经更改了外部变量foo,所以打印结果为:3

    js中先提升函数,后提升变量。

    思考以下代码:

    (function(){
        console.log(a)
        var a =1;
        function a(){}
        console.log(a)
    })()
    

    执行上述代码,我们可以看到控制台中按顺序依次打印:a(){},1。按照刚才的理解,js引擎将上面的代码会理解为下面的格式

    (function(){
        var a;
        console.log(a)
        a = 1;
        function a(){}
        console.log(a)
    })()
    

    那打印的结果应该为 undefined , f(){},这是因为我们忽略了一点,js先提升函数,后提升变量。所以正确的格式为

    (function(){
        function a(){}
        var a;
        console.log(a)
        a = 1;
       console.log(a)
    })()
    

    说明:
    1.定义变量a时,因为已经存在命名为a的函数,所以第一次打印结果为a(){}
    2.a=1,将变量a重新赋值,所以第二次打印结果为1

    小结

    • ES5中,使用var定义变量,变量的作用域有两种:全局作用域、函数作用域
    • var定义变量存在变量提升,此外,先提升函数,后提升变量

    但是开发过程中,变量提升往往会对开发造成困扰,幸好ES6中引入了let语法。


    let

    块级作用域

    我们刚才提到,ES5中,js只用两种作用域:全局作用域与函数作用域。在ES6中,let关键字会隐式地创建一个块级作用域(通常是{}内部),变量只能在这个作用域中被访问。例如题目一中

    for(var i=0;i<10;i++){
        var a='a'
        let b = ‘b'
    }
    console.log(a)
    console.log(b)
    

    我们在循环的内部,使用let创建了变量b,在循环外部访问时报错,b is not defined.就是这个原因。
    块级作用域的引入大大改善了代码中由于全局变量而引发的错误,比如文章开头提出的第二题:

    for(var i=0;i<3;i++){
        setTimeout(function(){
            console.log(i)
        },1000)
    }
    

    上述代码由于变量i是用var声明的,所以全局范围有效 ,当循环体执行完时,i=2,所以定时器中console.log(i)中的i是指向全局变量i的,所以打印结果为2,2,2
    如果我们将代码改为

    for(let i=0;i<3;i++){
        setTimeout(function(){
            console.log(i)
        },1000)
    }
    

    上述代码中,变量i使用let定义,所以只在本轮for循环中有效,所以打印结果为0,1,2。

    let不存在变量提升,其所声明的变量一定要在声明语句之后使用。

    例如:

    console.log(bar);
    let bar = 2;
    

    打印结果报错:bar is not defined

    此外,let 声明的变量不能重复声明,例如

    let foo = {n:1};
    (function(foo){
        console.log(foo.n);
        foo.n = 3;
        let foo = {n:2};
        console.log(foo.n);
    })(foo)
    console.log(foo.n);
    

    函数内部定义变量foo时,因为当前作用域中已经存在命名为foo的变量,所以报错:’foo’ has already been declared.

    const

    ES6中新增了let关键字的同时,也新增了const关键字。
    let与const有很多共同点

    • 都支持块级作用域
    • 都不支持变量提升
    • 都不支持重复声明

    此外,我们知道var声明全局变量时,变量是挂在window上的。而let,const声明变量,却不是。这样子便避免了window对象的变量污染问题。

    当然,const与let也有区别。const与let的区别在于:

    • let声明变量时无需赋值,const声明变量时必须赋值
    • let声明变量,变量可重新赋值,const声明变量,完成初始化后,值不得更改 (基本类型)

    刚刚提到const声明变量后,如果值类型是基本类型,则不得更改,如果是引用类型呢?

    如图所示,可修改。如果我用const定义变量,值为对象,但是想让对象的属性无法修改应该怎么做呢?


    对象属性的保护方法

    Object.defineProperty

    Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
    更多关于Object.defineProperty方法的信息,推荐阅读 MDN:Object.defineProperty

    function setConst(obj) {
          Object.keys(obj).forEach(function (t,i) {
            Object.defineProperty(obj,t,{
              value:obj[t],
              writable:false, // 是否可重新赋值
              enumerable:false, //是否可枚举
              configurable:false // 是否可删除,其他设置属性,是否可修改
            })
          })
        }
    

    如图所示,虽然实现了需求,对象属性无法修改,无法删除。可是控制台无提示,不是很人性化。幸好,ES6中提供了Proxy方法

    Proxy对象代理

    function setConst(obj) {
          return new Proxy(obj,{
            set:function (obj,prop,value) {
              throw new TypeError('Assignment to constant variable')
            },
            deleteProperty(target, key) {
              throw Error(`Error! ${key} cannot be deleted.`);
            }
          })
        }
    

  • 相关阅读:
    Java工程代码开发规范总结
    MySQL5.7 JSON字段性能测试
    Forest v1.5.12 发布,声明式 HTTP 框架,已超过 1.6k star
    HTTP基础系列之:一文搞懂URL
    近1000 star,Forest 1.5.0 正式版发布
    我的开源项目在五个月内超过了 600 star
    我的开源经历:为了方便处理三方 HTTP 接口而写的 Java 框架
    【约瑟夫环】C语言数组法+java循环链表法
    UNICODE编码 特殊符号
    swift3.0 保存图片到本地,申请权限
  • 原文地址:https://www.cnblogs.com/yxqd/p/10365072.html
Copyright © 2020-2023  润新知