[TOC]

一 作用域

1.1 作用域概述

通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
即:变量在某个范围内起作用和效果

作用:
提高了程序逻辑的局部性,增强了程序的可靠性,减少了命名冲突。

JavaScript(es6前)中的作用域有两种:

  • 全局作用域
  • 局部作用域(函数作用域)

    <script>
          //num在全局作用域
          var num = 10;
          console.log(num);//10
    
          function fun(){
              //现在这个num在局部作用域
              var num = 20;
              console.log(num);//20
          }
          fun()
    
          //这里打印的是全局作用域中的num 因为局部作用域中的num在函数执行完之后就销毁了
          console.log(num);
      </script>
    

    1.2 全局作用域

    作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。

    1.3 局部作用域

    作用于函数内的代码环境,就是局部作用域。因为跟函数有关系,所以也称为函数作用域。

    1.4 块级作用域

  • 块作用域由 { } 包括。

  • 在其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使用,如下面的Java代码:
    java有块级作用域:
    以上java代码会报错,是因为代码中 { } 即一块作用域,其中声明的变量 num,在 “{ }” 之外不能使用;
    而与之类似的JavaScript代码,则不会报错:
    Js中没有块级作用域(在ES6之前)

    if(true){
    int num = 123;
    system.out.print(num);  // 123
    }
    system.out.print(num);    // 报错
    
    <script>
         //es6新增的概念 块级作用域
         if(3<5){
             var num = 10  //num就在块作用域中
         }
         console.log(num);//es6之前使用var定义的变量是可以访问的
     </script>
    

    二 变量的作用域

    在JavaScript中,根据作用域的不同,变量可以分为两种:

  • 全局变量

  • 局部变量

    <script>
          //num是全局变量
          var num = 10;
          console.log(num);//10
    
          function fun(a){
              //num是局部变量
              var num1 = 20;
              console.log(num1);//20
    
              //全局变量可以在函数内部使用
              console.log(num);//10
          }
          fun()
    
          //报错,说明函数的参数并不是一个全局变量,而是一个局部变量,出了函数就无法使用
          console.log(a);
    
          //报错,局部变量出了函数就不能使用
          console.log(num1);
      </script>
    

    2.1 全局变量

    在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。

  • 全局变量在代码的任何位置都可以使用

  • 在全局作用域下 var 声明的变量 是全局变量
  • 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)

    <script>
          /* 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用) */
    
          function fun(){
              a = 10  //没有用var修饰的变量,也是全局变量,但是不推荐这么做
              console.log(a);
          }
    
          fun()//10
    
          console.log(a);//10
      </script>
    

    2.2 局部变量

    在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)

  • 局部变量只能在该函数内部使用

  • 在函数内部 var 声明的变量是局部变量
  • 函数的形参实际上就是局部变量

    2.3 二者区别

  • 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存

  • 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间

    三 作用域链

    3.1 概念

    内部函数访问外部函数的变量,采取的是链式查找的方式来决定取哪个值,这种结构我们称为作用域链

总结规律: 就近原则

 <script>
        /*
            只要是代码都一个作用域中,
            写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;
            如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;
            根据在**[内部函数可以访问外部函数变量]**的这种机制,
            用链式查找决定哪些数据能被内部函数访问,就称作作用域链
        */
       var num = 10

       function fn(){
           var num = 20

           function fun(){
               console.log(num);//就近原则
           }

           fun()
       }

       fn()

    </script>

3.2 案例分析

function f1() {
    var num = 123;
    function f2() {
        console.log( num );
    }
    f2();
}
var num = 456;
f1();
var a = 1;
function fn1() {
    var a = 2;
    var b = '22';
    fn2();
    function fn2() {
        var a = 3;
        fn3();
        function fn3() {
            var a = 4;
            console.log(a); //a的值 ?
            console.log(b); //b的值 ?
        }
    }
}
fn1();

四 预解析(预编译)

JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。
JavaScript 解析器在运行 JavaScript 代码的时候分为两步
预解析代码执行

  • 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。
  • 代码执行: 从上到下执行JS语句。
    预解析会把变量和函数的声明在代码执行之前执行完成。

    4.1 变量预解析

    预解析也叫做变量、函数提升。
    变量提升(变量预解析): 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。

4.2 函数预解析

函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。

4.3 预解析练习

<script>
        /*
            js引擎运行js的时候 分两步
                第一步:预解析(预编译)
                第二步:执行代码

            预解析:js引擎会把js中所有的var,还有function 提升到当前作用域的最前面
            执行代码:按照代码的书写顺序,从上往下执行

            预解析又分为变量预解析(变量提升)和函数预解析(函数提升)

            变量预解析  
                就是把所有变量的声明提升到当前作用域的最前面,不会提升赋值操作

            函数预解析
                就是把所有函数声明提升到当前作用域的最前面, 不会调用函数

        */

        //第一个问题
        //console.log(num);//报错

        //第二个问题
        console.log(num1);//undefined
        var num1 = 10
        console.log(num1);

        //执行顺序
        //var num1
        //console.log(num1);//undefined
        //num1 = 10


        //第三个问题
        fn()//11
        function fn(){
            console.log(11);
        }

        //执行顺序
        // function fn(){
        //     console.log(11);
        // }
        // fun()

        //第四个问题
        fun()//报错
        var fun = function (){//这种写法又称函数表达式,他的调用必须写在表达式的下面
            console.log(22);
        }

        //执行顺序
        //var fun
        //fun()//这一步就报错了 因为fun是一个undefined,不能调用
        //var fun = function (){
        //    console.log(22);
        //}
    </script>
<script>
        //预解析案例
        //案例1
        // var num = 10;
        // fun();

        // function fun() {
        //     console.log(num);
        //     var num = 20;
        // }

        //执行顺序
        // var num //undefined

        // function fun() {
        //     console.log(num);//undefined
        //     var num = 20;
        // }

        // num = 10 

        // fun();  



        // 案例2
        // var num = 10;

        // function fn() {
        //     console.log(num);
        //     var num = 20;
        //     console.log(num);
        // }
        // fn(); 

        //执行顺序
        // var num //undefined
        // function fn() {
        //     console.log(num);//undefined
        //     var num = 20;
        //     console.log(num);//20
        // }

        // num = 10

        // fn()




        // 案例3
        // var a = 18;
        // f1();

        // function f1() {
        //     var b = 9;
        //     console.log(a);
        //     console.log(b);
        //     var a = '123';
        // }  

        //执行顺序
        // var a  //undefined

        // function f1() {
        //     var b = 9;
        //     console.log(a);//undefined
        //     console.log(b);//9
        //     var a = '123';
        // }

        // a = 18

        // f1()

        // 案例4
        f1();
        console.log(c);
        console.log(b);
        console.log(a);

        function f1() {
            var a = b = c = 9;
            console.log(a);
            console.log(b);
            console.log(c);
        }

        //执行顺序
        /* function f1() {
            var a = b = c = 9;
            //相当于 var a = 9;  b=9; c=9  (b和c是直接赋值的9,前面没有var定义)
            //这里 b 和 c是全局变量 a是局部变量 所有a出了函数就不能使用了
            console.log(a);//9
            console.log(b);//9
            console.log(c);//9
        } 
        f1()
        console.log(c);//9
        console.log(b);//9
        console.log(a);//不能 因为a是函数内部的局部变量,出了函数就无法使用 */

    </script>

五 原型和this指向

5.1 对象的三种创建方式—复习

  1. 字面量方式

    var obj1 = {
    uname:'张三',
    age:23,
    sing:function(){
    console.log('我会唱歌');
    }
    }
    console.log(obj1);
    
  2. 使用new Object( )

    var obj2 = new Object()  //在js中称之为基类(祖宗),所有类的父类
        obj2.name='李四'
        obj2.age = 24
        obj2.sing=function(){
            console.log('我会唱歌');
        }
    console.log(obj2);
    
  3. 构造函数方式

    function Person(name,age){
    this.name = name;
    this.age = age;
    this.sing = function(){//方法
       console.log('我会唱歌');
    }
    }
    var obj = new Person('zs',12);
    

    5.2 静态成员和实例成员

    5.2.1实例成员

    实例成员就是构造函数内部通过this添加的成员
    如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问

    function Star(uname, age) {
     this.uname = uname;
     this.age = age;
     this.sing = function() {
     console.log('我会唱歌');
    }
    }
    var ldh = new Star('刘德华', 18);
    console.log(ldh.uname);//实例成员只能通过实例化的对象来访问
    

    5.2.2 静态成员

    静态成员 是在构造函数本身上添加的成员
    如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问

    function Star(uname, age) {
     this.uname = uname;
     this.age = age;
     this.sing = function() {
     console.log('我会唱歌');
    }
    }
    Star.sex = '男';
    var ldh = new Star('刘德华', 18);
    console.log(Star.sex);//静态成员只能通过构造函数来访问
    

    5.3 构造函数的问题

    构造函数方法很好用,但是存在浪费内存的问题。
    image.png

    <script>
        function Star(uname,age){
            this.uname = uname//属性
            this.age = age
            this.sing = function(){//方法
                console.log('我会唱歌');
            } 
        }
    
        var ldh = new Star('刘德华',58)
        var zxy = new Star('张学友',66)
    
        console.log(ldh.sing === zxy.sing);//fasle表明这两个不是同一个内存空间 
    
        console.dir(Star);//打印构造函数,看他里面有没有一个protoType对象  是有的
    
        /* 
         new 在执行的时候会做4件事情
         1,在内存中创建一个空的对象
         2,让this指向这个新的对象
         3,执行构造函数里面的代码,给这新对象添加属性和方法
         4,返回这个新对象
        */
    
        //产生的问题:每创建一个对象,都会为里面的方法分配一个内存空间,存在浪费内存的问题
    
    </script>
    

    5.4 构造函数原型prototype

    概念
    原型是一个对象,每一个构造函数都有一个prototype属性,指向这个对象。

作用
可以把那些不变的方法,直接定义在prototype对象(原型对象)上,这样所有对象的实例就可以共享这些方法。

总结
一般情况下,我们会把属性定义到构造函数里面,公共的方法放在原型对象上面,可以节省内存

<script>
        /*
        JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
        我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
        */
        function Star(uname,age){
            this.uname = uname//属性
            this.age = age
            /* this.sing = function(){//方法
                console.log('我会唱歌');
            } */
        }

        Star.prototype.sing = function(){
            console.log('我会唱歌');
        }

        var ldh = new Star('刘德华',58)
        var zxy = new Star('张学友',66)

        console.log(ldh.sing === zxy.sing);//现在就打印的是true.说明对象共享同一个方法

        console.dir(Star);//打印构造函数,看他里面有没有一个protoType对象  是有的

    </script>

5.5 对象原型 proto

概念
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象
proto就称为对象的原型

作用
为对象的查找机制提供一个方向,对象在调用方法的时候,就会通过proto到原型对象(prototype)里面去查找有没有对应方法,有的话,就执行
image.png

<script>
        function Star(uname,age){
            this.uname = uname//属性
            this.age = age
        }

        //方法定义在原型对象prototype上面
        Star.prototype.sing = function(){
            console.log('我会唱歌');
        }

        var ldh = new Star('刘德华',58)
        var zxy = new Star('张学友',66)
        console.log(ldh);

        //说明对象中有一个__proto__属性指向了 构造函数的 原型对象(Star.prototype)
        //对象在调用sing方法的时候,先去Star中找,如果没有的话,在去Star的原型对象prototype找
        console.log(ldh.__proto__ === Star.prototype);//true

        //__proto__就叫做对象的原型  指向 构造函数的原型对象
        ldh.sing()

    </script>

5.6 constructor构造函数

概念
对象原型(proto)和构造函数原型对象(prototype)里面都有一个属性 constructor 属性 ,constructor我们称为构造函数,因为它指回构造函数本身

作用
主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

<script>
        function Star(uname,age){
            this.uname = uname//属性
            this.age = age
        }

        //方法定义在原型对象prototype上面
        Star.prototype.sing = function(){
            console.log('我会唱歌');
        }

        var ldh = new Star('刘德华',58)
        var zxy = new Star('张学友',66)

        //验证
        //对象的原型( __proto__)和构造函数原型对象(prototype)里面都有一个属性 constructor 属性 
        console.log(ldh.__proto__);
        console.log(Star.prototype);

        //验证 __proto__  和  prototype 他们里面 constructor属性的值 就是当前的构造函数
        console.log(ldh.__proto__.constructor);
        console.log(Star.prototype.constructor);
    </script>

5.7 原型链

概念
每一个实例对象又有一个proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链.

作用
当实例对象调用方法的时候,首先通过proto属性找到构造函数的原型对象,看它里面有没有,没有的话再往上一层找,找到最后,如果都没有的话,就报错.
image.png

<script>
        function Star(uname,age){
            this.uname = uname//属性
            this.age = age
        }

        Star.prototype.sing = function(){
            console.log('我会唱歌');
        }

        var ldh = new Star('刘德华',58)
        var zxy = new Star('张学友',66)


        console.log(Star.prototype);//打印的是原型对象,他也是一个对象,他有没有原型对象

        //说明Star.prototype的对象的原型__proto__ 是指向Object的
        console.log(Star.prototype.__proto__ == Object.prototype);//true

        //在看看Object的原型对象是什么
        console.log(Object.prototype.__proto__);//null

    </script>

5.8 构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的proto属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
image.png

5.9 原型链查找机制

查找机制
1,当访问一个对象的属性(包括方法),首先查找这个对象自身有没有该属性(方法)
2,如果没有就查找他的原型(就是proto指向的prototype原型对象)
3,如果还没有就查找原型对象的原型(Object的原型对象)
4,依次类推,一直找到Object为止
5,proto对象的原型的意义就是为对象成员的查找机制提供了一个方向(线路)

 <script>
        function Star(uname,age){
            this.uname = uname
            this.age=age
        }

        Star.prototype.sing=function sing(){
            console.log('我会唱歌');
        }

        var ldh = new Star('刘德华',58)
        //只要是对象,就有这个__proto__属性,指向原型对象
        console.log(ldh.__proto__);
        console.log(ldh.__proto__ === Star.prototype);
        //原型对象又是一个对象,他里面也有__proto__属性,这个属性其实是执向的是Object的原型对象
        console.log(ldh.__proto__ .__proto__  === Object.prototype);
        //Object.prototype也是一个对象,他里面也有__proto__属性,指向null
        console.log(Object.prototype.__proto__);
    </script>
<script>
        /*
            查找机制
            1,当访问一个对象的属性(包括方法),首先查找这个对象自身有没有该属性(方法)
            2,如果没有就查找他的原型(就是__proto__指向的prototype原型对象)
            3,如果还没有就查找原型对象的原型(Object的原型对象)
            4,依次类推,一直找到Object为止
            5,__proto__对象的原型的意义就是为对象成员的查找机制提供了一个方向(线路)
        */

        function Star(uname,age){
            this.uname = uname
            this.age=age
        }

        Star.prototype.sing=function sing(){
            console.log('我会唱歌');
        }

        //Object.prototype.sex='男'
        //Star.prototype.sex='女'

        var ldh = new Star('刘德华',58)

        //ldh.sex='男'

        console.log(ldh.sex);

 </script>

5.10 原型对象中this指向

构造函数中的this和原型对象的this,都指向我们new出来的实例对象
构造函数中this指向 —> 对象的实例
原型对象中this指向 —> 对象的实例

<script>

        function Star(uname,age){
            this.uname = uname
            this.age=age
            //在构造函数中,里面的this指向对象实例
            console.log(this);//这里打印的就是对象的实例
        }

        Star.prototype.sing=function sing(){
            console.log('我会唱歌');
            //在原型对象中,里面的this指向对象实例
            console.log(this);//这里打印的就是对象的实例
        }

        var ldh = new Star('刘德华',58)
        ldh.sing()

    </script>

5.11 通过原型为数组扩展内置方法

通过原型可以为内置对象扩展自定义方法
例如: 为Array内置对象自定义数组求和方法

<script>
        console.dir(Array.prototype);

        //给数组内置对象添加一个求和的方法(sum)
        //将来只需要调用这个方法,就可以获取数组元素的和

        Array.prototype.sum=function(){
            //定义一个求和变量
            var sum = 0
            //遍历数组元素进行累加
            for(var i=0;i <this.length;i++ ){
                sum += this[i]
            }
            //返回结果
            return sum
        }

        //调用
        var arr = [1,2,3]
        var sum = arr.sum()
        console.log(sum);

        var arr1 = new Array(4,5,6)
        var sum1 = arr1.sum()
        console.log(sum1);

    </script>

六 函数进阶

6.1 函数的定义方式

  1. 方式1 函数声明方式 function 关键字 (命名函数)

    function fn(){}
    
  2. 方式2 函数表达式(匿名函数)

    var fn = function(){}
    
  3. 方式3 new Function()
    ```javascript var f = new Function(‘a’, ‘b’, ‘console.log(a + b)’); f(1, 2);

var fn = new Function(‘参数1’,’参数2’…, ‘函数体’) 注意 /Function 里面参数都必须是字符串格式 第三种方式执行效率低,也不方便书写,因此较少使用 所有函数都是 Function 的实例(对象)
函数也属于对象
/

<a name="1ebe181f"></a>
### 6.2 函数的调用
```javascript
/* 1. 普通函数 */
function fn() {
    console.log('人生的巅峰');
}
 fn(); 

/* 2. 对象的方法 */
var o = {
  sayHi: function() {
      console.log('人生的巅峰');
  }
}
o.sayHi();

/* 3. 构造函数*/
function Star() {};
new Star();

/* 4. 绑定事件函数*/
 btn.onclick = function() {};   // 点击了按钮就可以调用这个函数

/* 5. 定时器函数*/
setInterval(function() {}, 1000);  这个函数是定时器自动1秒钟调用一次

/* 6. 立即执行函数(自调用函数)*/
(function() {
    console.log('人生的巅峰');
})();

6.3 函数内部的this指向

这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同
一般指向我们的调用者.
image.png

<button>按钮</button>
    <script>
        //函数的不同调用方式,决定了this的指向不同
        //1,普通函数  this执行window
        function fn(){
            console.log('普通函数:'+this);
        }
        window.fn()

        //2,对象方法  this指向对象obj
        var obj = {
            study:function(){
                console.log('对象方法:'+this);
            }
        }
        obj.study()

        //3,构造函数  this 执行实例对象 stu
        function Student(){}
        Student.prototype.sing=function(){
            console.log('构造函数:'+this);
        }
        var stu = new Student()
        stu.sing()

        //4,绑定事件函数  this 指向的是函数的调用者 btn这个按钮对象
        var btn = document.querySelector('button')
        btn.onclick=function(){
            console.log('绑定事件函数:'+this);
        }

        //5,定时器函数  this 指向的也是window
        window.setTimeout(function(){
            console.log('定时器函数:'+this);
        },1000);

        //6,立即执行函数  this 指向的也是window --- 前面语句一定要加上分号结束
        (function(){
            console.log('立即调用函数:'+this);
        })()

    </script>

6.4 改变函数内部 this 指向

call( )方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向.

call的作用:
1,可以改变函数内的this指向
2,可以调用函数 格式: 函数名.call( )

<script>

        var obj = {
            name:'张三'
        }

        function fn(a,b){
            console.log(this);
            console.log(a+b);
        }

        //fn()

        //改变指向  这里this指向对象实例obj
        fn.call(obj,1,2) 
        //call 两个作用
            //1,可以调用函数 格式:函数名.call()
            //2,可以改变函数内的this指向

</script>

6.5 高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
(或)
函数作为参数或者返回值,这种函数,叫做高阶函数

<script>
        /*
            函数也是一种数据类型,同样也可以作为参数或者返回值使用
        */

         function fn(a,b,f){
            console.log(a+b);
            f&&f()//如果存在这个函数,就调用它  && 符号在前面为假的时候,就不会执行后面的语句了
         }

         fn(1,2,function(){
             console.log('我是后面调用的');
         })
</script>

七 闭包

7.1 变量的作用域复习

变量根据作用域的不同分为两种:全局变量和局部变量。

  1. 函数内部可以使用全局变量。
  2. 函数外部不可以使用局部变量。
  3. 当函数执行完毕,本作用域内的局部变量会销毁。

    7.2 什么是闭包

    闭包(closure)指有权访问另一个函数作用域中变量的函数。那个变量所在的函数就是闭包.

    <script>
         //闭包指有权访问另一个函数作用域中变量的函数
         //fun这个内部函数访问了fn这个外部函数的局部变量num
         //局部变量num所在的函数就是闭包
    
         function fn(){
             var num = 10
    
             function fun(){
                 console.log(num);
             }
             fun()
         }
    
         fn()
     </script>
    

    7.3闭包的作用

    作用:延伸变量的作用范围。

    function fn() {
    var num = 10;
    function fun() {
        console.log(num);
      }
     return fun;
    }
    var f = fn();
    f();
    
    <script>
         //fn外面的作用域 访问 fn内部的局部变量
    
         //作用:延伸了变量的作用范围  之前num只能在fn内部调用,现在我们可以从外面调用num
    
         function fn(){
             var num = 100
    
             /* function fun(){
                 console.log(num);
             } */
    
             //fun()
             //return fun//第一个操作,将内部的函数返回
    
             return function(){  //简化操作:直接将内部的函数返回
                 console.log(num);
             }
    
         }
       var f =  fn()//第二步操作,在外部接收返回的内部函数
       f()//第三步操作,调用
    
     </script>
    

    八 let和const

    8.1 let

ES6中新增了用于声明变量的关键字

let声明的变量只在所处于的块级有效

 if (true) { 
     let a = 10;
 }
console.log(a) // a is not defined

注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。

不存在变量提升

console.log(a); // a is not defined 
let a = 20;

暂时性死区

利用let声明的变量会绑定在这个块级作用域,不会受外界的影响

 var tmp = 123;
 if (true) { 
     tmp = 'abc';
     let tmp; 
 }

经典面试题

var arr = [];
 for (var i = 0; i < 2; i++) {
     arr[i] = function () {
         console.log(i); 
     }
 }
 arr[0]();
 arr[1]();

小结

  • let关键字就是用来声明变量的
  • 使用let关键字声明的变量具有块级作用域
  • 在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的
  • 防止循环变量变成全局变量
  • 使用let关键字声明的变量没有变量提升
  • 使用let关键字声明的变量具有暂时性死区特性

    <script>
          //1,let关键字就是用来声明变量的
          /* let a = 10
          console.log(a); */
    
          //2,使用let关键字声明的变量有块级作用域,var关键字就不具备这个特点
          /* if(true){
              var a = 100;
              let b = 200;
              console.log(a);
              console.log(b);
          }
          console.log(a);//100
          console.log(b);//报错 */
    
          //4,防止循环变量变成全局变量
          /* for(var i =0;i<3;i++){
              console.log(i);
          }
          console.log(i); */
    
          /* for(let i =0;i<3;i++){
              console.log(i);
          }
          console.log(i); */
    
          //5,使用let关键字声明的变量没有变量提升
         /*  console.log(a);
          let a = 10 */
    
          //6.使用let声明的变量不能重复定义
          /* let a = 10
          let a = 10
          console.log(a); */
    
          //使用let声明的变量具有暂时性死区
          //ES6明确规定,如果块级作用域中,存在let和const命令,这个块级作用域声明的变量,从一开始就
          //形成了封闭的作用域,凡是在声明之前使用这些变量就会报错
          //在代码块内,使用let命令声明变量之前,该变量都是不可使用的,这种语法称之为暂时性死区
          var num = 10
          if(true){
              console.log(num);//这个区域已经被let声明的num控制住了,在num声明之前是不能使用的
              let num = 100
          }
    
      </script>
    

    8.2 const

    声明常量,常量就是值(内存地址)不能变化的量

    具有块级作用域

    if (true) { 
       const a = 10;
    }
    console.log(a) // a is not defined
    

    声明常量时必须赋值

    const PI; // Missing initializer in const declaration
    

    常量赋值后,值不能修改

    ```javascript const PI = 3.14; PI = 100; // Assignment to constant variable.

const ary = [100, 200]; ary[0] = ‘a’; ary[1] = ‘b’; console.log(ary); // [‘a’, ‘b’]; ary = [‘a’, ‘b’]; // Assignment to constant variable.

<a name="5db9fd7c-1"></a>
#### 小结

- const声明的变量是一个常量
- 既然是常量不能重新进行赋值,如果是基本数据类型,不能更改值,如果是复杂数据类型,不能更改地址值
- 声明 const时候必须要给定值
```javascript
<script>
        //声明常量,常量就是值(内存地址)不能变化的量

        //1,使用const声明的常量具有块级作用域
        /* if(true){
            const a = 10
            console.log(a)
        }
        console.log(a)//报错 */

        //2,使用const声明的常量必须赋初值
        /* 
            var a 
            let b 
            const c //报错
        */

        //3.常量声明后值不可以更改
       /*  const a = 100
        a = 200//报错 */

        const arr = [10,20]
        arr[0] = 100 //这里没有报错,因为仅仅是修改了arr里面的内容
        arr[1] = 200
        console.log(arr);

        //arr = [1,2] //报错:改变arr的指向,让他指向了别的数组

        const stu = {
            name:'张三',
            age:23
        }

        stu.name = '李四'
        console.log(stu);

        /* stu = {
            name:'李四',
            age:24
        } */

    </script>

8.3 let、const、var 的区别及应用场景

区别

  • 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
  • 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
  • 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值

image.png

应用场景

   建议使用优先级: const > let > var

const好处:
1,阅读代码的人看到之后就会认为这个值是不能修改的,防止了无意间修改变量值导致的错误
2,浏览器对const进行了优化,可以提高代码的执行效率

let好处
1.没有变量提升
2,有块级作用域

let一般用于基本数据类型,const一般用于引用数据类型(函数,对象,字符串这些)
如果定义的变量,不能被修改,一定使用const

九 解构赋值

ES6中允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构

解构概念: 把数据进行分解, 依次给变量赋值

9.1 数组解构

 let [a, b, c] = [1, 2, 3];
 console.log(a)//1
 console.log(b)//2
 console.log(c)//3
//如果解构不成功,变量的值为undefined

9.2 对象解构

 let person = { name: 'zhangsan', age: 20 }; 
 let { name, age } = person;
 console.log(name); // 'zhangsan' 
 console.log(age); // 20

 let {name: myName, age: myAge} = person; // myName myAge 属于别名
 console.log(myName); // 'zhangsan' 
 console.log(myAge); // 20

9.3 小结

  • 解构赋值就是把数据结构分解,然后给变量进行赋值
  • 如果结构不成功,变量跟数值个数不匹配的时候,变量的值为undefined
  • 数组解构用中括号包裹,多个变量用逗号隔开,对象解构用花括号包裹,多个变量用逗号隔开
  • 利用解构赋值能够让我们方便的去取对象中的属性跟方法

    <script>
          const p = {
              name:'小三',
              age:23,
              sex:'女'
          }
    
          const{name,age,sex,hight} = p
    
          console.log(name);
          console.log(age);
          console.log(sex);
          console.log(hight);//没有东西匹配就是undefined
    
          //:后面就是别名
          /* const{name:myName,age:myAge,sex:mySex} = p
          console.log(myName);
          console.log(myAge);
          console.log(mySex); */
    
      </script>
    

    十 箭头函数

    10.1 语法

    ES6中新增的定义函数的方式。
    箭头函数简化了函数的写法

    () => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
    const fn = () => {}//代表把一个函数赋值给fn
    

    函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号

    function sum(num1, num2) { 
       return num1 + num2; 
    }
    //es6写法
    const sum = (num1, num2) => num1 + num2;
    

    如果形参只有一个,可以省略小括号

    function fn (v) {
       return v;
    } 
    //es6写法
    const fn = v => v;
    

    箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this

    const obj = { name: '张三'} 
    function fn () { 
       console.log(this);//this 指向 是obj对象
       return () => { 
           console.log(this);//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
       } 
    } 
    const resFn = fn.call(obj); 
    resFn();
    

    10.2 小结

  • 箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置,可以简单理解成,定义箭头函数中的作用域的this指向谁,它就指向谁

  • 箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题 ```javascript