美文网首页
ES6新的声明方式

ES6新的声明方式

作者: 考拉狸猫 | 来源:发表于2021-02-22 15:30 被阅读0次

    作用域

    很多编程语言就是在变量中存储值,并且能读取和修改此值。
    变量该存储在哪,又给如何读取?所以程序制定了一些规则:作用域。
    作用域:是可访问变量的集合。
    常见的作用域主要分为几个类型:全局作用域(global/window)、函数作用域(function)、块状作用域({})、动态作用域(this)

    全局作用域

    变量在函数或者代码块 {} 外定义,即为全局作用域。不过,在函数或者代码块 {} 内未定义的变量也是拥有全局作用域的(不推荐)

    var name= "es"
    // 此处可调用 name变量
    function myFunction() {
        // 函数内可调用 name变量
    }
    

    name拥有作用域,可以在任意地方被读取获取修改
    注意
    没有使用 var 关键字,该变量依然为全局变量,因为它将作为 global 或者 window 的属性存在。

    function myFunction() {
      name= "es"
    }
    

    函数作用域

    在函数内部定义的变量,就是局部作用域。函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!

    function bar() {
        var testValue = 'inner'
    }
    console.log(testValue) // 报错:ReferenceError: testValue is not defined
    

    如果想读取函数内的变量,必须借助 return 或者闭包
    闭包,外层作用域想访问函数内部的作用域(变量)。

    return 方式外层作用域访问函数内部的作用域

    function bar(value) {
        var testValue = 'inner'
    
        return testValue + value
    }
    console.log(bar('fun')) // "innerfun"
    

    闭包方式外层作用域访问函数内部的作用域

    function bar(value) {
        var testValue = 'inner'
        var rusult = testValue + value
        function innser() {
            return rusult
        }
        return innser()
    }
    console.log(bar('fun')) // "innerfun"
    

    return 是函数对外交流的出口,而 return 可以返回的是函数,函数内部的子函数是可以获取函数作用域内的变量的。

    问题:如果 inner 函数再嵌套函数呢?
    如果 inner 函数再嵌套函数呢?这就涉及到作用域链

    2021-02-23_094154.jpg

    上图是作用域链,和原型链相似,任何一个作用域链都是一个堆栈,首先先把全局作用域压入栈底,再按照函数的嵌套关系一次压入堆栈。在执行的时候就按照这个作用域链寻找变量。
    堆栈:堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。
    原型链图

    2021-02-05_144550.jpg

    块状作用域
    于什么是块,只要认识 {} 就可以了

    if (true) {
        let a = 1
        console.log(a)
    }
    

    if 后 {} 就是“块”,这个里面的变量就是拥有这个块状作用域,按照规则, {} 之外是无法访问这个变量的。

    动态作用域

    window.a = 3
    function test() {
        console.log(this.a)
    }
    test.bind({
        a: 2
    })() // 2
    test() // 3
    

    test.bind已经把作用域的范围进行了修改指向了 { a: 2 },而 this 指向的是当前作用域对象。

    问题
    作用域是在代码编写的时候就已经决定了呢,还是在代码执行的过程中才决定的?

    var name= " es"
    // 此处可调用 name变量
    function myFunc() {
        // 函数内可调用 name变量
    }
    

    name就是全局作用域,函数内部的用 var 定义的变量就是函数作用域。这个也就是专业术语:词法作用域
    变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。
    相反,只能在执行阶段才能决定变量的作用域,那就是动态作用域。

    新的声明方式

    let变量

    1. let 声明的全局变量不是全局对象window的属性
    let不能通过 window. 变量名 的方式访问这些变量,var 声明的全局变量是 window 的属性,是可以通过 window. 变量名 的方式访问的

    var a = 5
    console.log(window.a) // 5
    
    let a = 5
    console.log(window.a) // undefined
    

    2. 用let定义变量不允许重复声明
    使用 var 可以重复定义,使用 let 却不可以

    var a = 5
    var a = 6
    console.log(a) // 5
    
    let a = 5
    let a = 6
    // VM131:1 Uncaught SyntaxError: Identifier 'a' has already been declared
    //   at <anonymous>:1:1
    

    3. let声明的变量不存在变量提升
    a 的调用在声明之前,所以它的值是 undefined

    function foo() {
        console.log(a)
        var a = 5
    }
    
    foo() //undefined
    // var 会导致变量提升,上述代码和下面的代码等同
    function foo() {
        var a
        console.log(a)
        a = 5
    }
    
    foo() //undefined
    

    对于 let 而言,变量的调用是不能先于声明的

    function foo() {
        console.log(a)
        let a = 5
    }
    
    foo()
    // Uncaught ReferenceError: Cannot access 'a' before initialization
    

    4. let声明的变量具有暂时性死区
    只要块级作用域内存在 let 命令,它所声明的变量就绑定在了这个区域,不再受外部的影响。
    在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

    var a = 5
    if (true) {
        a = 6
        let a
    }
    

    4. let 声明的变量拥有块级作用域

    {
        let a = 5
    }
    console.log(a) // undefined
    

    a 变量是在代码块 {} 中使用 let 定义的,它的作用域是这个代码块内部,外部无法访问

    for 循环

    for (var i = 0 i < 3 i++) {
        console.log('循环内:' + i) // 0、1、2
    }
    console.log('循环外:' + i) // 3
    

    改成let声明

    for (let i = 0 i < 3 i++) {
        console.log('循环内:' + i) // 0、1、2
    }
    console.log('循环外:' + i) // ReferenceError: i is not defined
    

    加上setTimeout
    https://www.cnblogs.com/planetwithpig/p/12016231.html
    babel 网址:https://www.babeljs.cn/repl

     function time(){
               for(var i=0;i<5;i++){
                       setTimeout(function(){
                           console.log('setTimeout的i',i) //5个5
                    },0)
              }
        }
      time();
    

    1、setTimeOut() 是一个异步函数,JS遇到异步函数的时候,会把异步函数插入到队列中等待。也就是所谓的插队。
    2、 流程:for(i=0) ——> for(i=1) ——> for(i=2) for(i=3) ——> for(i=4) ——> for(i=5)ps:(这段循环都内完成了)——>console.log(5) ——> console.log(5) ——> console.log(5) ——> console.log(5) ——> console.log(5) ——> 执行完成
    3、 for循环完成之后,i经过5次循环,开始执行setTimeout方法的时候i的值以及变成了5,因为setTimeout有5次方法调用,所以输出5个5

    希望的值是0、1、2,.....也就是每次保存住循环时候 i 的值
    方案1:闭包
    思路:因为setTimeOut()是异步执行,所以让它立即执行就可以了

          for(var i=0;i<5;i++){
                    (function(i){
                        setTimeout(function(){
                           console.log('使用闭包的i',i)   //0,1,2,3,4
                        })
                    })(i)
                }
    

    闭包检测到setTimeOut时不再放到队列中进行等待,而是立即运行setTimeOut()
    执行流程如下:
    for(i=0) ——> console.log(0) ——> for(i=1) ——> console.log(1) ——> for(i=2) ——> console.log(2) ——> for(i=3) ——> console.log(3) ——> for(i=4) ——> console.log(4) ——> for(i=5) ——> 执行结束
    内部函数调用外部函数的变量,以确保外部函数的变量不会被释放,内部函数的值引用着外部函数的值

    方案2:使用let

       for (let i = 0; i < 5; i++) { 
                    setTimeout(function() { 
                        console.log(i); //0,1,2,3,4
                    }, 1000 * i); 
               } 
    

    let的作用域是块作用域,所以每次JS检测到setTimeOut,把setTimeOut放到队列的同时,let定义的i的值也会跟随setTimeOut进去队列。所以每次循环后队列里的setTimeOut里的i的值是不一样的
    var定义的i是无法进入setTimeOut的。i只能在运行到setTimeOut时才会向外层环境申请i的值,而这个时候i的值已经变成5了。

    同步和异步


    2021-02-22_164036.jpg

    异步执行原理
    https://www.cnblogs.com/lvzl/p/14242510.html

    const { log } = console;
    log(1); // 首先呢,JS代码是从上至下逐行执行,到这里先打印 1
    setTimeout(() => { // 到了这里,遇到了异步任务,把异步操作加到异步队列中,然后接着往下执行JS代码
      log(2);
    });
    new Promise((resolve, reject) => { 
      log(3); // 执行到这里,这里的代码也是同步的,因此打印 3
      resolve(); // resolve 执行以后会进入.then, .then里面也是异步执行, 因此加入异步队列,整个的JS代码第一次就执行完了
    }).then(() => {
      log(4);
    });
    // 现在异步队列中有两个任务, setTimeout,Promise.then. JS在执行下一个宏任务之前会保证微任务队列为空,因此会先打印 4, 再打印 3
    // 微任务: Promise.then, process.nextTick(node)
    // 宏任务: 整体的JS代码, setTimeout, setInterval
    // 正确答案: 1324
    
    2089555-20210106174701043-1416802229.jpg

    Const

    不能被改变的叫做常量
    const 除了具有 let 的块级作用域和不会变量提升外,还有就是它定义的是常量,在用 const 定义变量后,我们就不能修改它了,对变量的修改会抛出异常

    const PI = 3.1415
    console.log(PI)
    PI = 5
    console.log(PI)
    // Uncaught TypeError: Assignment to constant variable.
    

    这个代码块中因为对 PI 尝试修改,导致浏览器报错,这就说明 const 定义的变量是不能被修改的,它是只读的。

    重点

    const obj = {
        name: 'xiaozhang',
        age: 34
    }
    obj.name= 'xiaowu'
    console.log(obj)
    // {name: "xiaowu", age: 34"}
    

    const 定义的 obj 竟然被改变了...
    这个时候就要去了解js的变量是如何存储的
    栈: 原始数据类型(Undefined,Null,Boolean,Number、String)
    堆: 引用数据类型(对象、数组、函数)
    基本数据类型存储在 栈内存 中,引用数据类型存储在 堆内存 中然后在栈内存中保存 引用地址


    2021-02-22_171357.jpg

    const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

    如何让对象或者数组这种引用数据类型也不被改变呢?

    Object.freeze(obj)
    

    Object.freeze() 只是浅层冻结,只会对最近一层的对象进行冻结,并不会对深层对象冻结。

    相关文章

      网友评论

          本文标题:ES6新的声明方式

          本文链接:https://www.haomeiwen.com/subject/gmztfltx.html