黑马程序员前端JavaScript高级——ES6学习笔记

简单看下开头目录,里面包含了面向对象的编程思想,以及构造函数,类和对象、原型链、箭头函数、数组和字符串新增方法、函数递归、深拷贝浅拷贝、正则表达式等知识点。本文是对CSDN其他博主记录的一个补充,因为黑马也是今年才陆续补充一些知识点的视频的,本文将其收录于此,雷同勿怪。

目录

Day 1 - JavaScript 面向对象

01 - 学习目标(target)

02 - 面向对象编程介绍

面向过程编程 POP(Process- oriented programming)

面向对象编程 OOP(Object Oriented Programming)

03 - ES6 中的类和对象

对象

类 class

创建类和对象:

类添加方法:

04 - 类的继承

super 关键字

使用类注意事项

Day 2 - 构造函数和原型

01 - 学习目标(target)

02 - 构造函数和原型

概述

构造函数

构造函数原型 prototype 

对象原型 __proto__ 

constructor 构造函数

构造函数、实例、原型对象三者之间的关系

原型链

JavaScript 的成员查找机制(规则)

原型对象this指向

扩展内置对象

03 - 继承

call()方法

借用构造函数继承父类型属性

借用原型对象继承父类型方法

ES6 类的本质

04 - ES5 中的新增方法

概述

数组方法

字符串方法

对象方法

Day 3 - 函数进阶

01 - 学习目标(target) 

02 - 函数的定义和调用

函数的定义方式  

函数的调用方式

03 - this 的指向 

改变函数内部 this 指向

call apply bind 总结

04 - 严格模式 

什么是严格模式

开启严格模式

严格模式中的变化

05 - 高阶函数

06 - 闭包

变量作用域

什么是闭包

在 chrome 中调试闭包 

闭包的作用

闭包应用 --- 点击 li 输出索引号

闭包案例

闭包总结

07 - 递归

什么是递归

利用递归求数学题

浅拷贝和深拷贝

Day 4 - 正则表达式

01 - 学习目标(target)

02 - 正则表达式概述

什么是正则表达式

正则表达式的特点

03 - 正则表达式在 JavaScript 中的使用

创建正则表达式

测试正则表达式 test

04 - 正则表达式中的特殊字符

正则表达式的组成 

边界符

字符类

量词符

案例:用户名验证

括号总结

预定义类

案例:座机号验证

05 - 正则表达式中的替换

replace 替换

正则表达式参数

案例:敏感词过滤

Day 5 - ES6部分知识 

01 - 学习目标(target )

02 - ES6简介

什么是ES6

为什么使用ES6

03 - ES6 的新增语法

let 关键字

经典面试题

const 关键字

let、const、var 的区别

解构赋值

箭头函数

剩余参数

04 - ES6 的内置对象扩展

Array 的扩展方法 

String 的扩展方法

Set 数据结构


Day 1 - JavaScript 面向对象

01 - 学习目标(target)

  • 能够说出什么是面向对象
  • 能够说出类和对象的关系
  • 能够使用 class 创建自定义类型
  • 能够说出什么是继承

02 - 面向对象编程介绍

面向过程编程 POP(Process- oriented programming)

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依 次调用就可以了。

面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。

面向对象编程 OOP(Object Oriented Programming)

面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。

举个例子:将大象装进冰箱,面向对象做法。

先找出对象,并写出这些对象的功能:

1. 大象对象

● 进去

2. 冰箱对象

● 打开

● 关闭

3. 使用大象和冰箱的功能

面向对象是以对象功能来划分问题,而不是步骤。

面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。

其特性:

  • 封装性
  • 继承性
  • 多态性

面向过程和面向对象的对比

面向过程 面向对象
优点 性能比面向对象高,适合跟硬件联系很紧密 的东西,例如单片机就采用的面向过程编程。 易维护、易复用、易扩展,由于面向对象有 封装、继承、多态性的特性,可以设计出低耦合的 系统,使系统 更加灵活、更加易于维护
缺点 没有面向对象易维护、易复用、易扩展 性能比面向过程低

03 - ES6 中的类和对象

对象

 对象是一个具体的事物在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、 函数等。

 对象是由属性方法组成的:

  • 属性:事物的特征,在对象中用属性来表示(常用名词)
  • 方法:事物的行为,在对象中用方法来表示(常用动词)

类 class

在 ES6 中新增的类概念,可以用关键字 class 声明一个类,之后以这个类来实例化对象。

抽象了对象的公共部分,它泛指某一大类(class)。

对象特指某一个,通过类实例化一个具体的对象。

面向对象的思维特点:

1. 抽取(抽象)对象共用的属性和行为组织(封装)成一个(模板)

2. 对类进行实例化, 获取类的对象

创建类和对象:

<script>
// 1. 创建类 class  创建一个 明星类
    class Star {
       constructor(uname, age) {
          this.uname = uname;
          this.age = age;
       }
    }

// 2. 利用类创建对象 new
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 20);
    console.log(ldh);
    console.log(zxy);
//(1) 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
//(2) 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
//(3) constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
//(4) 生成实例 new 不能省略
//(5) 最后注意语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
</script>

注意:类必须使用 new 实例化对象;

constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义,,类内部会自动给我们创建一个 constructor()

类添加方法:

<script>
// 1. 创建类 class  创建一个 明星类
   class Star {
   // 类的共有属性放到 constructor 里面
   constructor(uname, age) {
       this.uname = uname;
       this.age = age;
    }
    sing(song) {
    // console.log('我唱歌');
       console.log(this.uname + song);

       }
    }

// 2. 利用类创建对象 new
   var ldh = new Star('刘德华', 18);
   var zxy = new Star('张学友', 20);
   console.log(ldh);
   console.log(zxy);
// (1) 我们类里面所有的函数不需要写function 
// (2) 多个函数方法之间不需要添加逗号分隔
   ldh.sing('冰雨');
   zxy.sing('李香兰');
</script>

04 - 类的继承

程序中的继承:子类可以继承父类的一些属性和方法。

语法:

super 关键字

super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。

语法:

注意:子类在构造函数中使用 super, 必须放到 this 前面 (必须先调用父类的构造方法,在使用子类构造方法)

类的继承实例: 

 <script>
        // 1. 类的继承
        // class Father {
        //     constructor() {

        //     }
        //     money() {
        //         console.log(100);

        //     }
        // }
        // class Son extends Father {

        // }
        // var son = new Son();
        // son.money();
        class Father {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }
            sum() {
                console.log(this.x + this.y);

            }
        }
        class Son extends Father {
            constructor(x, y) {
                super(x, y); //调用了父类中的构造函数
            }
        }
        var son = new Son(1, 2);
        var son1 = new Son(11, 22);
        son.sum();
        son1.sum();
</script>

super 关键字调用父类普通函数 实例 

<script>
// super 关键字调用父类普通函数
   class Father {
     say() {
        return '我是爸爸';
     }
   }
   class Son extends Father {
     say() {
     // console.log('我是儿子');
        console.log(super.say() + '的儿子');
     // super.say() 就是调用父类中的普通函数 say()
           }
   }
   var son = new Son();
   son.say();
// 继承中的属性或者方法查找原则: 就近原则
// 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
// 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
</script>

 子类继承父类同时继承扩展自己的方法:

<script>
// 父类有加法方法
   class Father {
      constructor(x, y) {
          this.x = x;
          this.y = y;
         }
          sum() {
            console.log(this.x + this.y);
         }
      }
// 子类继承父类加法方法 同时 扩展减法方法
   class Son extends Father {
      constructor(x, y) {
      // 利用super 调用父类的构造函数
      // super 必须在子类this之前调用
            super(x, y);
            this.x = x;
            this.y = y;

         }
         subtract() {
           console.log(this.x - this.y);

         }
      }
   var son = new Son(5, 3);
   son.subtract();
   son.sum();
</script>

使用类注意事项

三个注意点:

  1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象。
  2. 类里面的共有属性和方法一定要加 this 使用。
  3. 类里面的 this 指向问题。
  4. constructor 里面的 this 指向实例对象, 方法里面的 this 指向这个方法的调用者。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <button>点击</button>
    <script>
        var that;
        var _that;
        class Star {
            constructor(uname, age) {
                // constructor 里面的this 指向的是 创建的实例对象
                that = this;
                console.log(this);

                this.uname = uname;
                this.age = age;
                // this.sing();
                this.btn = document.querySelector('button');
                this.btn.onclick = this.sing;
            }
            sing() {
                // 这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
                console.log(this);

                console.log(that.uname); // that里面存储的是constructor里面的this
            }
            dance() {
                // 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
                _that = this;
                console.log(this);

            }
        }

        var ldh = new Star('刘德华');
        console.log(that === ldh);
        ldh.dance();
        console.log(_that === ldh);

        // 1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象

        // 2. 类里面的共有的属性和方法一定要加this使用.
    </script>
</body>

</html

Day 2 - 构造函数和原型

01 - 学习目标(target)

  •  能够使用构造函数创建对象
  • 能够说出原型的作用
  • 能够说出访问对象成员的规则
  • 能够使用 ES5 新增的一些方法

02 - 构造函数和原型

概述

在 ES6 之前,JS 中并没有引入类的概念。ES6, 全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏 览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。

在 ES6之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。

创建对象可以通过三种方式:( ES6 之前的方法)

  • 对象字面量
  • new   Object()
  • 自定义构造函数
<script>
   // 1. 利用 new Object() 创建对象

      var obj1 = new Object();

   // 2. 利用 对象字面量创建对象

      var obj2 = {};

   // 3. 利用构造函数创建对象
      function Star(uname, age) {
         this.uname = uname;
         this.age = age;
         this.sing = function() {
            console.log('我会唱歌');

         }
      }

      var ldh = new Star('刘德华', 18);
      var zxy = new Star('张学友', 19);
      console.log(ldh);
      ldh.sing();
      zxy.sing();
</script>

构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我 们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

在 JS 中,使用构造函数时要注意以下两点:

  • 构造函数用于创建某一类对象,其首字母要大写
  • 构造函数要和 new 一起使用才有意义

new 在执行时会做四件事情:

① 在内存中创建一个新的空对象。

② 让 this 指向这个新的对象。

③ 执行构造函数里面的代码,给这个新对象添加属性和方法。

④ 返回这个新对象(所以构造函数里面不需要 return )。

JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员实例成员

  • 静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
<script>
    // 构造函数中的属性和方法我们称为成员, 成员可以添加
    function Star(uname, age) {
       this.uname = uname;
       this.age = age;
       this.sing = function() {
           console.log('我会唱歌');

          }
    }
    var ldh = new Star('刘德华', 18);
    // 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
    // 实例成员只能通过实例化的对象来访问
    console.log(ldh.uname);
    ldh.sing();
    // console.log(Star.uname); // 不可以通过构造函数来访问实例成员
    // 2. 静态成员 在构造函数本身上添加的成员  sex 就是静态成员
    Star.sex = '男';
    // 静态成员只能通过构造函数来访问
    console.log(Star.sex);
    console.log(ldh.sex); // 不能通过对象来访问
</script>

构造函数方法虽然很好用,但是存在浪费内存的问题

比如:

function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing)  // false

 我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?

构造函数原型 prototype 

构造函数通过原型分配的函数是所有对象所共享的。 

JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一 个对象,这个对象的所有属性和方法,都会被构造函数所拥有 

我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。 

因此,上述例子可进行如下修改 :

<script>
   // 1. 构造函数的问题. 
   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('刘德华', 18);
   var zxy = new Star('张学友', 19);
   console.log(ldh.sing === zxy.sing);
   // console.dir(Star);
   ldh.sing();
   zxy.sing();
   // 2. 一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
</script>

 因此,原型是什么?

答:一个对象,我们也称为 prototype原型对象

原型的作用是什么?

答:共享方法

对象原型 __proto__ 

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。

  • __proto__对象原型和原型对象 prototype 是等价的 ;
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性, 因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype。
 <script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype.sing = function() {
            console.log('我会唱歌');
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    ldh.sing();
  console.log(ldh); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象prototype
  console.log(ldh.__proto__ === Star.prototype);
  // 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
  // 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
</script>

constructor 构造函数

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

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

一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋 值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

<script>
   function Star(uname, age) {
      this.uname = uname;
      this.age = age;
   }
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
// Star.prototype.sing = function() {
//     console.log('我会唱歌');
// };
// Star.prototype.movie = function() {
//     console.log('我会演电影');
// }
   Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
       constructor: Star,
       sing: function() {
          console.log('我会唱歌');
       },
       movie: function() {
            console.log('我会演电影');
       }
   }
   var ldh = new Star('刘德华', 18);
   var zxy = new Star('张学友', 19);
   console.log(Star.prototype);
   console.log(ldh.__proto__);
   console.log(Star.prototype.constructor);
   console.log(ldh.__proto__.constructor);
</script>

构造函数、实例、原型对象三者之间的关系

原型链

 <script>
     function Star(uname, age) {
         this.uname = uname;
         this.age = age;
     }
     Star.prototype.sing = function() {
         console.log('我会唱歌');
     }
     var ldh = new Star('刘德华', 18);
     // 1. 只要是对象就有__proto__ 原型, 指向原型对象
     console.log(Star.prototype);
     console.log(Star.prototype.__proto__ === Object.prototype);
     // 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
     console.log(Object.prototype.__proto__);
     // 3. 我们Object.prototype原型对象里面的__proto__原型  指向为 null
</script>

JavaScript 的成员查找机制(规则)

① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。

③ 如果还没有就查找原型对象的原型(Object的原型对象)。

④ 依此类推一直找到 Object 为止(null)。

⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype.sing = function() {
        console.log('我会唱歌');

    }
    Star.prototype.sex = '女';
    // Object.prototype.sex = '男';
    var ldh = new Star('刘德华', 18);
    ldh.sex = '男';
    console.log(ldh.sex);
    console.log(Object.prototype);
    console.log(ldh);
    console.log(Star.prototype);
    console.log(ldh.toString());
</script>

原型对象this指向

构造函数中的 this 指向我们实例对象。

原型对象里面放的是方法,这个方法里面的 this 指向的是这个方法的调用者, 也就是这个实例对象。 

 <script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    var that;
    Star.prototype.sing = function() {
        console.log('我会唱歌');
        that = this;
    }
    var ldh = new Star('刘德华', 18);
    // 1. 在构造函数中,里面this指向的是对象实例 ldh
    ldh.sing();
    console.log(that === ldh);

    // 2.原型对象函数里面的this 指向的是 实例对象 ldh
</script>

扩展内置对象

可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。

注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。 

<script>
    // 原型对象的应用 扩展内置对象方法
    Array.prototype.sum = function() {
        var sum = 0;
        for (var i = 0; i < this.length; i++) {
            sum += this[i];
        }
        return 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];
    console.log(arr.sum());
    console.log(Array.prototype);
    var arr1 = new Array(11, 22, 33);
    console.log(arr1.sum());
</script>

03 - 继承

ES6 之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。

call()方法

调用这个函数, 并且修改函数运行时的 this 指向

  • thisArg :当前调用函数 this 的指向对象
  • arg1,arg2:传递的其他参数
<script>
    // call 方法
    function fn(x, y) {
        console.log('我想喝手磨咖啡');
        console.log(this);
        console.log(x + y);


    }
    var o = {
        name: 'andy'
    };
    // fn();
    // 1. call() 可以调用函数
    // fn.call();
    // 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
    fn.call(o, 1, 2);
</script>

借用构造函数继承父类型属性

核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        // this 指向父构造函数的对象实例
        this.uname = uname;
        this.age = age;
    }
    // 2 .子构造函数 
    function Son(uname, age, score) {
        // this 指向子构造函数的对象实例
        Father.call(this, uname, age);
        this.score = score;
    }
    var son = new Son('刘德华', 18, 100);
    console.log(son);
</script>

借用原型对象继承父类型方法

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。 

核心原理:

① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类() 

② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象

③ 将子类的 constructor 从新指向子类的构造函数

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        // this 指向父构造函数的对象实例
        this.uname = uname;
        this.age = age;
    }
    Father.prototype.money = function() {
        console.log(100000);

    };
    // 2 .子构造函数 
    function Son(uname, age, score) {
        // this 指向子构造函数的对象实例
        Father.call(this, uname, age);
        this.score = score;
    }
    // Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
    Son.prototype = new Father();
    // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
    Son.prototype.constructor = Son;
    // 这个是子构造函数专门的方法
    Son.prototype.exam = function() {
        console.log('孩子要考试');

    }
    var son = new Son('刘德华', 18, 100);
    console.log(son);
    console.log(Father.prototype);
    console.log(Son.prototype.constructor);
</script>

ES6 类的本质

  1.  class 本质还是 function。
  2. 类的所有方法都定义在类的 prototype 属性上
  3. 类创建的实例,里面也有 __proto__ 指向类的 prototype 原型对象
  4. 所以 ES6 的类它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
  5. .所以 ES6 的类其实就是语法糖
  6. 语法糖:语法糖就是一种便捷写法。 简单理解,有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖
<script>
    // ES6 之前通过 构造函数+ 原型实现面向对象 编程
    // (1) 构造函数有原型对象prototype 
    // (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
    // (3) 构造函数可以通过原型对象添加方法
    // (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
    // ES6 通过 类 实现面向对象编程 
    class Star {

    }
    console.log(typeof Star);
    // 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
    // (1) 类有原型对象prototype 
    console.log(Star.prototype);
    // (2) 类原型对象prototype 里面有constructor 指向类本身
    console.log(Star.prototype.constructor);
    // (3)类可以通过原型对象添加方法
    Star.prototype.sing = function() {
        console.log('冰雨');

    }
    var ldh = new Star();
    console.dir(ldh);
    // (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
    console.log(ldh.__proto__ === Star.prototype);
    i = i + 1;
    i++
</script>

04 - ES5 中的新增方法

概述

ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:

  • 数组方法
  • 字符串方法
  • 对象方法

数组方法

迭代(遍历)方法:forEach()、map()、filter()、some()、every();

forEach() 方法

  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
<script>
    // forEach 迭代(遍历) 数组
    var arr = [1, 2, 3];
    var sum = 0;
    arr.forEach(function(value, index, array) {
        console.log('每个数组元素' + value);
        console.log('每个数组元素的索引号' + index);
        console.log('数组本身' + array);
        sum += value;
    })
    console.log(sum);
</script>

filter() 方法

  • filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组 
  • 注意它直接返回一个新数组
  • currentValue: 数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
<script>
    // filter 筛选数组
    var arr = [12, 66, 4, 88, 3, 7];
    var newArr = arr.filter(function(value, index) {
        // return value >= 20;
        return value % 2 === 0;
    });
    console.log(newArr);
</script>

some() 方法

 

  •  some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
  • 注意它返回值是布尔值, 如果查找到这个元素, 就返回 true , 如果查找不到就返回 false。
  • 如果找到第一个满足条件的元素,则终止循环. 不在继续查找。
  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
<script>
    // some 查找数组中是否有满足条件的元素 
    // var arr = [10, 30, 4];
    // var flag = arr.some(function(value) {
    //     // return value >= 20;
    //     return value < 3;
    // });
    // console.log(flag);
    var arr1 = ['red', 'pink', 'blue'];
    var flag1 = arr1.some(function(value) {
        return value == 'pink';
    });
    console.log(flag1);
    // 1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
    // 2. some 也是查找满足条件的元素是否存在  返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环
</script>

 forEach() 和 some 的区别

<script>
    var arr = ['red', 'green', 'blue', 'pink'];
    // 1. forEach迭代 遍历
    // arr.forEach(function(value) {
    //     if (value == 'green') {
    //         console.log('找到了该元素');
    //         return true; // 在forEach 里面 return 不会终止迭代
    //     }
    //     console.log(11);

    // })
    // 如果查询数组中唯一的元素, 用some方法更合适,
    arr.some(function(value) {
        if (value == 'green') {
            console.log('找到了该元素');
            return true; //  在some 里面 遇到 return true 就是终止遍历 迭代效率更高
        }
        console.log(11);

    });
    // arr.filter(function(value) {
    //     if (value == 'green') {
    //         console.log('找到了该元素');
    //         return true; //  // filter 里面 return 不会终止迭代
    //     }
    //     console.log(11);

    // });
</script>

字符串方法

trim() 方法会从一个字符串的两端删除空白字符。

trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <input type="text"> <button>点击</button>
    <div></div>
    <script>
        // trim 方法去除字符串两侧空格
        var str = '   an  dy   ';
        console.log(str);
        var str1 = str.trim();
        console.log(str1);
        var input = document.querySelector('input');
        var btn = document.querySelector('button');
        var div = document.querySelector('div');
        btn.onclick = function() {
            var str = input.value.trim();
            if (str === '') {
                alert('请输入内容');
            } else {
                console.log(str);
                console.log(str.length);
                div.innerHTML = str;
            }
        }
    </script>
</body>

</html>

对象方法

1. Object.keys() 用于获取对象自身所有的属性

  • 效果类似 for…in 
  • 返回一个由属性名组成的数组
<script>
    // 用于获取对象自身所有的属性
    var obj = {
        id: 1,
        pname: '小米',
        price: 1999,
        num: 2000
    };
    var arr = Object.keys(obj);
    console.log(arr);
    arr.forEach(function(value) {
        console.log(value);

    })
</script>

2. Object.defineProperty() 定义对象中新属性或修改原有的属性。(了解)

  • obj:必需。目标对象 
  • prop:必需。需定义或修改的属性的名字
  • descriptor:必需。目标属性所拥有的特性

Object.defineProperty() 第三个参数 descriptor 说明: 以对象形式 { } 书写

  • value:设置属性的值 默认为 undefined
  • writable:值是否可以重写。true | false 默认为 false
  • enumerable:目标属性是否可以被枚举。true | false 默认为 false
  • configurable:目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为 false
<script>
    // Object.defineProperty() 定义新属性或修改原有的属性
    var obj = {
        id: 1,
        pname: '小米',
        price: 1999
    };
    // 1. 以前的对象添加和修改属性的方式
    // obj.num = 1000;
    // obj.price = 99;
    // console.log(obj);
    // 2. Object.defineProperty() 定义新属性或修改原有的属性
    Object.defineProperty(obj, 'num', {
        value: 1000,
        enumerable: true
    });
    console.log(obj);
    Object.defineProperty(obj, 'price', {
        value: 9.9
    });
    console.log(obj);
    Object.defineProperty(obj, 'id', {
        // 如果值为false 不允许修改这个属性值 默认值也是false
        writable: false,
    });
    obj.id = 2;
    console.log(obj);
    Object.defineProperty(obj, 'address', {
        value: '中国山东蓝翔技校xx单元',
        // 如果只为false 不允许修改这个属性值 默认值也是false
        writable: false,
        // enumerable 如果值为false 则不允许遍历, 默认的值是 false
        enumerable: false,
        // configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false
        configurable: false
    });
    console.log(obj);
    console.log(Object.keys(obj));
    delete obj.address;
    console.log(obj);
    delete obj.pname;
    console.log(obj);
    Object.defineProperty(obj, 'address', {
        value: '中国山东蓝翔技校xx单元',
        // 如果值为false 不允许修改这个属性值 默认值也是false
        writable: true,
        // enumerable 如果值为false 则不允许遍历, 默认的值是 false
        enumerable: true,
        // configurable 如果为false 则不允许删除这个属性 默认为false
        configurable: true
    });
    console.log(obj.address);
</script>

Day 3 - 函数进阶

01 - 学习目标(target) 

  • 能够说出函数的多种定义和调用方式 
  • 能够说出和改变函数内部 this 的指向
  • 能够说出严格模式的特点
  • 能够把函数作为参数和返回值传递
  • 能够说出闭包的作用
  • 能够说出递归的两个条件
  • 能够说出深拷贝和浅拷贝的区别

02 - 函数的定义和调用

函数的定义方式  

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

2. 函数表达式 (匿名函数)

3. new Function()

  • Function 里面参数都必须是字符串格式 
  • 第三种方式执行效率低,也不方便书写,因此较少使用
  • 所有函数都是 Function 的实例(对象)
  • 函数也属于对象

<script>
    //  函数的定义方式

    // 1. 自定义函数(命名函数) 

    function fn() {};

    // 2. 函数表达式 (匿名函数)

    var fun = function() {};


    // 3. 利用 new Function('参数1','参数2', '函数体');

    var f = new Function('a', 'b', 'console.log(a + b)');
    f(1, 2);
    // 4. 所有函数都是 Function 的实例(对象)
    console.dir(f);
    // 5. 函数也属于对象
    console.log(f instanceof Object);
</script>

函数的调用方式

<script>
    // 函数的调用方式

    // 1. 普通函数
    function fn() {
        console.log('人生的巅峰');

    }
    // fn();   fn.call()
    // 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('人生的巅峰');
    })();
    // 立即执行函数是自动调用
</script>

03 - this 的指向 

原则

this 的指向是在我们调用函数的时候确定的。调用方式的不同决定了 this 的指向不同。一般指向的是我们的调用者。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <button>点击</button>
    <script>
        // 函数的不同调用方式决定了this 的指向不同
        // 1. 普通函数 this 指向window
        function fn() {
            console.log('普通函数的this' + this);
        }
        window.fn();
        // 2. 对象的方法 this指向的是对象 o
        var o = {
            sayHi: function() {
                console.log('对象方法的this:' + this);
            }
        }
        o.sayHi();
        // 3. 构造函数 this 指向 ldh 这个实例对象 原型对象里面的this 指向的也是 ldh这个实例对象
        function Star() {};
        Star.prototype.sing = function() {

        }
        var ldh = new Star();
        // 4. 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
        var btn = document.querySelector('button');
        btn.onclick = function() {
            console.log('绑定时间函数的this:' + this);
        };
        // 5. 定时器函数 this 指向的也是window
        window.setTimeout(function() {
            console.log('定时器的this:' + this);

        }, 1000);
        // 6. 立即执行函数 this还是指向window
        (function() {
            console.log('立即执行函数的this' + this);
        })();
    </script>
</body>

</html>

改变函数内部 this 指向

JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有 call()、apply()、bind()三种方法。

1. call 方法 

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

语法结构为:

fun.call(thisArg, arg1, arg2, ...) 
  •  thisArg:在 fun 函数运行时指定的 this 值;
  • arg1,arg2:传递的其他参数;
  • 返回值就是函数的返回值,因为它就是调用函;
  • 因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承。
<script>
    // 改变函数内this指向  js提供了三种方法  call()  apply()  bind()

    // 1. call()
    var o = {
        name: 'andy'
    }

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

    };
    fn.call(o, 1, 2);
    // call 第一个可以调用函数 第二个可以改变函数内的this 指向
    // call 的主要作用可以实现继承
    function Father(uname, age, sex) {
        this.uname = uname;
        this.age = age;
        this.sex = sex;
    }

    function Son(uname, age, sex) {
        Father.call(this, uname, age, sex);
    }
    var son = new Son('刘德华', 18, '男');
    console.log(son);
</script>

 2. apply 方法

apply() 方法调用一个函数。简单理解为调用函数的方式,它也可以改变函数的 this 指向。

语法结构为:

fun.apply(thisArg, [argsArray])
  • thisArg:在 fun 函数运行时指定的 this 值;
  • argsArray:传递的值,必须包含在数组里面;
  • 返回值就是函数的返回值,因为它就是调用函数;
  • 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值。
<script>
    // 改变函数内this指向  js提供了三种方法  call()  apply()  bind()

    // 2. apply()  应用 运用的意思
    var o = {
        name: 'andy'
    };

    function fn(arr) {
        console.log(this);
        console.log(arr); // 'pink'

    };
    fn.apply(o, ['pink']);
    // 1. 也是调用函数 第二个可以改变函数内部的this指向
    // 2. 但是他的参数必须是数组(伪数组)
    // 3. apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求数组最大值 
    // Math.max();
    var arr = [1, 66, 3, 99, 4];
    var arr1 = ['red', 'pink'];
    // var max = Math.max.apply(null, arr);
    var max = Math.max.apply(Math, arr);
    var min = Math.min.apply(Math, arr);
    console.log(max, min);
</script>

 3. bind 方法

bind() 方法不会调用函数。但是能改变函数内部 this 指向。

语法结构为:

fun.bind(thisArg, arg1, arg2, ...)
  • thisArg:在 fun 函数运行时指定的 this 值;
  • arg1,arg2:传递的其他参数;
  • 返回由指定的 this 值和初始化参数改造的原函数拷贝;
  • 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <button>点击</button>
    <button>点击</button>
    <button>点击</button>
    <script>
        // 改变函数内this指向  js提供了三种方法  call()  apply()  bind()

        // 3. bind()  绑定 捆绑的意思
        var o = {
            name: 'andy'
        };

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


        };
        var f = fn.bind(o, 1, 2);
        f();
        // 1. 不会调用原来的函数   可以改变原来函数内部的this 指向
        // 2. 返回的是原函数改变this之后产生的新函数
        // 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
        // 4. 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
        // var btn1 = document.querySelector('button');
        // btn1.onclick = function() {
        //     this.disabled = true; // 这个this 指向的是 btn 这个按钮
        //     // var that = this;
        //     setTimeout(function() {
        //         // that.disabled = false; // 定时器函数里面的this 指向的是window
        //         this.disabled = false; // 此时定时器函数里面的this 指向的是btn
        //     }.bind(this), 3000); // 这个this 指向的是btn 这个对象
        // }
        var btns = document.querySelectorAll('button');
        for (var i = 0; i < btns.length; i++) {
            btns[i].onclick = function() {
                this.disabled = true;
                setTimeout(function() {
                    this.disabled = false;
                }.bind(this), 2000);
            }
        }
    </script>
</body>

</html>

call apply bind 总结

相同点:

 都可以改变函数内部的 this 指向。

区别点:

1. call 和 apply 会调用函数, 并且改变函数内部 this 指向;

2. call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg];

3. bind 不会调用函数, 可以改变函数内部this指向。

主要应用场景:

1. call 经常做继承;

2. apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值;

3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的 this 指向。

04 - 严格模式 

什么是严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。

严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

严格模式对正常的 JavaScript 语义做了一些更改:

1. 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。

2. 消除代码运行的一些不安全之处,保证代码运行的安全。

3. 提高编译器效率,增加运行速度。

4. 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比 如一些保留字如:class, enum, export, extends, import, super 不能做变量名

开启严格模式

严格模式可以应用到整个脚本个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式为函数开启严格模式两种情况。

1. 为脚本开启严格模式

有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件 放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。 

<script>
   (function (){
      "use strict";
      var num = 10;
      function fn() {}
    })();
</script>

 2. 为函数开启严格模式

要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前

function fn(){
    "use strict";
    return "这是严格模式。";
}

 将 "use strict" 放在函数体的第一行,则整个函数以 "严格模式" 运行。

<body>
    <!-- 为整个脚本(script标签)开启严格模式 -->
    <script>
        'use strict';
        //   下面的js 代码就会按照严格模式执行代码
    </script>
    <script>
        (function() {
            'use strict';
        })();
    </script>
    <!-- 为某个函数开启严格模式 -->
    <script>
        // 此时只是给fn函数开启严格模式
        function fn() {
            'use strict';
            // 下面的代码按照严格模式执行
        }

        function fun() {
            // 里面的还是按照普通模式执行
        }
    </script>
</body>

严格模式中的变化

严格模式对 Javascript 的语法和行为,都做了一些改变。

1. 变量规定

① 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后再使用。

② 严禁删除已经声明变量。例如,delete x; 语法是错误的。 

2. 严格模式下 this 指向问题

① 以前在全局作用域函数中的 this 指向 window 对象。

② 严格模式下全局作用域中函数中的 this 是 undefined。

③ 以前构造函数时不加 new 也可以 调用,当普通函数,this 指向全局对象

④ 严格模式下,如果 构造函数不加 new 调用, this 指向的是 undefined 如果给他赋值则会报错

⑤ new 实例化的构造函数指向创建的对象实例。

⑥ 定时器 this 还是指向 window 。

⑦ 事件、对象还是指向调用者。

3. 函数变化

① 函数不能有重名的参数

② 函数必须声明在顶层。新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。

更多严格模式要求参考:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

<script>
    'use strict';
    // 1. 我们的变量名必须先声明再使用
    // num = 10;
    // console.log(num);
    var num = 10;
    console.log(num);
    // 2.我们不能随意删除已经声明好的变量
    // delete num;
    // 3. 严格模式下全局作用域中函数中的 this 是 undefined。
    // function fn() {
    //     console.log(this); // undefined。

    // }
    // fn();
    // 4. 严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.
    // function Star() {
    //     this.sex = '男';
    // }
    // // Star();
    // var ldh = new Star();
    // console.log(ldh.sex);
    // 5. 定时器 this 还是指向 window 
    // setTimeout(function() {
    //     console.log(this);

    // }, 2000);
    // a = 1;
    // a = 2;
    // 6. 严格模式下函数里面的参数不允许有重名
    // function fn(a, a) {
    //     console.log(a + a);

    // };
    // fn(1, 2);
    function fn() {}
</script>

05 - 高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数将函数作为返回值输出

<script>
    function fn(callback){
      callback&&callback();
    }
    fn(function(){alert('hi')}
</script>
<script>
    function fn(){
      return function() {}
    }
    fn();
</script>

此时 fn 就是一个高阶函数

函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。 最典型的就是作为回调函数。

同理函数也可以作为返回值传递回来

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="jquery.min.js"></script>
    <style>
        div {
            position: absolute;
            width: 100px;
            height: 100px;
            background-color: pink;
        }
    </style>
</head>

<body>
    <div></div>
    <script>
        // 高阶函数- 函数可以作为参数传递
        function fn(a, b, callback) {
            console.log(a + b);
            callback && callback();
        }
        fn(1, 2, function() {
            console.log('我是最后调用的');

        });
        $("div").animate({
            left: 500
        }, function() {
            $("div").css("backgroundColor", "purple");
        })
    </script>
</body>

</html>

06 - 闭包

变量作用域

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

1. 函数内部可以使用全局变量。

2. 函数外部不可以使用局部变量。

3. 当函数执行完毕,本作用域内的局部变量会销毁。

什么是闭包

闭包(closure)指有权访问另一个函数作用域中变量函数。 ----- JavaScript 高级程序设计

简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。 

<script>
    function fn1(){ // fn1 就是闭包函数
      var num = 10;
      function fn2(){
        console.log(num); // 10
      }
    fn2()
    }
    fn1();
</script>
<script>
    // 闭包(closure)指有权访问另一个函数作用域中变量的函数。
    // 闭包: 我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量 num
    function fn() {
        var num = 10;

        function fun() {
            console.log(num);

        }
        fun();
    }
    fn();
</script>

在 chrome 中调试闭包 

1. 打开浏览器,按 F12 键启动 chrome 调试工具。

2. 设置断点。

3. 找到 Scope 选项(Scope 作用域的意思)。

4. 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。

5. 当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。

闭包的作用

<script>
    // 闭包(closure)指有权访问另一个函数作用域中变量的函数。
    // 一个作用域可以访问另外一个函数的局部变量 
    // 我们fn 外面的作用域可以访问fn 内部的局部变量
    // 闭包的主要作用: 延伸了变量的作用范围
    function fn() {
        var num = 10;

        // function fun() {
        //     console.log(num);

        // }
        // return fun;
        return function() {
            console.log(num);
        }
    }
    var f = fn();
    f();
    // 类似于
    // var f = function() {
    //         console.log(num);
    //     }
    // var f =  function fun() {
    //         console.log(num);

    //     }
</script>

闭包应用 --- 点击 li 输出索引号

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <ul class="nav">
        <li>榴莲</li>
        <li>臭豆腐</li>
        <li>鲱鱼罐头</li>
        <li>大猪蹄子</li>
    </ul>
    <script>
        // 闭包应用-点击li输出当前li的索引号
        // 1. 我们可以利用动态添加属性的方式
        var lis = document.querySelector('.nav').querySelectorAll('li');
        for (var i = 0; i < lis.length; i++) {
            lis[i].index = i;
            lis[i].onclick = function () {
                // console.log(i);
                console.log(this.index);

            }
        }
        // 2. 利用闭包的方式得到当前小li 的索引号
        for (var i = 0; i < lis.length; i++) {
            // 利用for循环创建了4个立即执行函数
            // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
            (function (i) {
                // console.log(i);
                lis[i].onclick = function () {
                    console.log(i);

                }
            })(i);
        }
    </script>
</body>

</html>

闭包案例

1. 循环注册点击事件。

2. 循环中的 setTimeout()。

3. 计算打车价格。

<script>
    // 闭包应用-计算打车价格 
    // 打车起步价13(3公里内),  之后每多一公里增加 5块钱.  用户输入公里数就可以计算打车价格
    // 如果有拥堵情况,总价格多收取10块钱拥堵费
    // function fn() {};
    // fn();
    var car = (function() {
        var start = 13; // 起步价  局部变量
        var total = 0; // 总价  局部变量
        return {
            // 正常的总价
            price: function(n) {
                if (n <= 3) {
                    total = start;
                } else {
                    total = start + (n - 3) * 5
                }
                return total;
            },
            // 拥堵之后的费用
            yd: function(flag) {
                return flag ? total + 10 : total;
            }
        }
    })();
    console.log(car.price(5)); // 23
    console.log(car.yd(true)); // 33

    console.log(car.price(1)); // 13
    console.log(car.yd(false)); // 13
</script>

思考题:

<script>
    // 思考题 1:

    var name = "The Window";
    var object = {
        name: "My Object",
        getNameFunc: function () {
            return function () {
                return this.name;
            };
        }
    };

    console.log(object.getNameFunc()())
    var f = object.getNameFunc();
    // 类似于
    var f = function () {
        return this.name;
    }
    f();

    // 思考题 2:

     var name = "The Window";
     var object = {
         name: "My Object",
         getNameFunc: function() {
             var that = this;
             return function() {
                 return that.name;
             };
         }
     };
     console.log(object.getNameFunc()())
     var f = object.getNameFunc();
     var f1 = function () {
         return that.name;
     };
     f1();
</script>

闭包总结

1. 闭包是什么?

闭包是一个函数 (一个作用域可以访问另外一个函数的局部变量)

2. 闭包的作用是什么?

延伸变量的作用范围

07 - 递归

什么是递归

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数

简单理解:函数内部自己调用自己,这个函数就是递归函数。

递归函数的作用和循环效果一样

由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return

<script>
    // 递归函数 : 函数内部自己调用自己, 这个函数就是递归函数
    var num = 1;

    function fn() {
        console.log('我要打印6句话');

        if (num == 6) {
            return; // 递归里面必须加退出条件
        }
        num++;
        fn();
    }
    fn();
</script>

利用递归求数学题

1. 求 1 * 2 *3 ... * n 阶乘。

<script>
    // 利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
    function fn(n) {
        if (n == 1) {
            return 1;
        }
        return n * fn(n - 1);
    }
    console.log(fn(3));
    console.log(fn(4));
    // 详细思路 假如用户输入的是3
    //return  3 * fn(2)
    //return  3 * (2 * fn(1))
    //return  3 * (2 * 1)
    //return  3 * (2)
    //return  6
</script>

2. 求斐波那契数列 。 

<script>
    // 利用递归函数求斐波那契数列(兔子序列)  1、1、2、3、5、8、13、21...
    // 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
    // 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
    function fb(n) {
        if (n === 1 || n === 2) {
            return 1;
        }
        return fb(n - 1) + fb(n - 2);
    }
    console.log(fb(3));
    console.log(fb(6));
</script>

3. 根据 id 返回对应的数据对象

<script>
    var data = [{
        id: 1,
        name: '家电',
        goods: [{
            id: 11,
            gname: '冰箱',
            goods: [{
                id: 111,
                gname: '海尔'
            }, {
                id: 112,
                gname: '美的'
            }, ]
        }, {
            id: 12,
            gname: '洗衣机'
        }]
    }, {
        id: 2,
        name: '服饰'
    }];
    // 我们想要做输入id号,就可以返回的数据对象
    // 1. 利用 forEach 去遍历里面的每一个对象
    function getID(json, id) {
        var o = {};
        json.forEach(function(item) {
            // console.log(item); // 2个数组元素
            if (item.id == id) {
                // console.log(item);
                o = item;
                // 2. 我们想要得里层的数据 11 12 可以利用递归函数
                // 里面应该有goods这个数组并且数组的长度不为 0 
            } else if (item.goods && item.goods.length > 0) {
                o = getID(item.goods, id);
            }

        });
        return o;
    }
    console.log(getID(data, 1));
    console.log(getID(data, 2));
    console.log(getID(data, 11));
    console.log(getID(data, 12));
    console.log(getID(data, 111));
</script>

浅拷贝和深拷贝

1. 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。

2. 深拷贝拷贝多层,每一级别的数据都会拷贝。

3. Object.assign(target, ...sources)  es6 新增方法可以浅拷贝

浅拷贝:

<script>
    // 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
    // 深拷贝拷贝多层, 每一级别的数据都会拷贝.
    var obj = {
        id: 1,
        name: 'andy',
        msg: {
            age: 18
        }
    };
    var o = {};
    // for (var k in obj) {
    //     // k 是属性名   obj[k] 属性值
    //     o[k] = obj[k];
    // }
    // console.log(o);
    // o.msg.age = 20;
    // console.log(obj);

    console.log('--------------');
    Object.assign(o, obj);
    console.log(o);
    o.msg.age = 20;
    console.log(obj);
</script>

深拷贝:

<script>
    // 深拷贝拷贝多层, 每一级别的数据都会拷贝.
    var obj = {
        id: 1,
        name: 'andy',
        msg: {
            age: 18
        },
        color: ['pink', 'red']
    };
    var o = {};
    // 封装函数 
    function deepCopy(newobj, oldobj) {
       for (var k in oldobj) {
            // 判断我们的属性值属于那种数据类型
            // 1. 获取属性值  oldobj[k]
            var item = oldobj[k];
            // 2. 判断这个值是否是数组
            if (item instanceof Array) {
                newobj[k] = [];
                deepCopy(newobj[k], item)
            } else if (item instanceof Object) {
                // 3. 判断这个值是否是对象
                newobj[k] = {};
                deepCopy(newobj[k], item)
            } else {
                // 4. 属于简单数据类型
                newobj[k] = item;
            }

        }
    }
    deepCopy(o, obj);
    console.log(o);

    var arr = [];
    console.log(arr instanceof Object);
    o.msg.age = 20;
    console.log(obj);
</script>

Day 4 - 正则表达式

01 - 学习目标(target)

  • 能够说出正则表达式的作用 
  • 能够写出简单的正则表达式
  • 能够使用正则表达式对表单进行验证
  • 能够使用正则表达式替换内容

02 - 正则表达式概述

什么是正则表达式

正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也 是对象。

正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字 母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一 些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。

其他语言也会使用正则表达式,本阶段我们主要是利用 JavaScript 正则表达式完成表单验证。 

正则表达式的特点

1. 灵活性、逻辑性和功能性非常的强。

2. 可以迅速地用极简单的方式达到字符串的复杂控制。

3. 对于刚接触的人来说,比较晦涩难懂。比如: ^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$

4. 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达 式。比如用户名:/^[a-z0-9_-]{3,16}$/ 

03 - 正则表达式在 JavaScript 中的使用

创建正则表达式

在 JavaScript 中,可以通过两种方式创建一个正则表达式。

1. 通过调用 RegExp 对象的构造函数创建

      var 变量名 = new RegExp(/表达式/);

2. 通过字面量创建

      var 变量名 = /表达式/ ;

// 注释:中间放表达式就是正则字面量

测试正则表达式 test

test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。

 regexObj.test(str)
  • regexObj 是写的正则表达式
  • str 我们要测试的文本
  • 就是检测 str 文本是否符合我们写的正则表达式规范。
<script>
    // 正则表达式在js中的使用

    // 1. 利用 RegExp对象来创建 正则表达式
    var regexp = new RegExp(/123/);
    console.log(regexp);

    // 2. 利用字面量创建 正则表达式
    var rg = /123/;
    // 3.test 方法用来检测字符串是否符合正则表达式要求的规范
    console.log(rg.test(123));
    console.log(rg.test('abc'));
</script>

04 - 正则表达式中的特殊字符

正则表达式的组成 

一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中 特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。 

特殊字符非常多,可以参考:

● MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

● jQuery 手册:正则表达式部分

● 正则测试工具:http://tool.oschina.net/regex

这里我们把元字符划分几类学习 。

边界符

正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。 

边界符 说明
^ 表示匹配行首的文本(以谁开始)
$ 表示匹配行尾的文本(以谁结束)

如果 ^ 和 $ 在一起,表示必须是精确匹配。

 <script>
    // 边界符 ^ $ 
    var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
    // /abc/ 只要包含有abc这个字符串返回的都是true
    console.log(rg.test('abc'));
    console.log(rg.test('abcd'));
    console.log(rg.test('aabcd'));
    console.log('---------------------------');
    var reg = /^abc/;
    console.log(reg.test('abc')); // true
    console.log(reg.test('abcd')); // true
    console.log(reg.test('aabcd')); // false
    console.log('---------------------------');
    var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
    console.log(reg1.test('abc')); // true
    console.log(reg1.test('abcd')); // false
    console.log(reg1.test('aabcd')); // false
    console.log(reg1.test('abcabc')); // false
</script>

字符类

字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。 

1. [] 方括号

/[abc]/.test('andy') // true 

 后面的字符串只要包含 abc 中任意一个字符,都返回 true 。

2. [-] 方括号内部范围符-

/^[a-z]$/.test(c') // true

方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以。

3. [^] 方括号内部 取反符^

/[^abc]/.test('andy') // false

方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。

注意和边界符 ^ 区别,边界符写到方括号外面。

4. 字符组合

/[a-z1-9]/.test('andy') // true

 方括号内部可以使用字符组合,这里表示包含 a 到 z 的26个英文字母和 1 到 9 的数字都可以。

<script>
    //var rg = /abc/;  只要包含abc就可以 
    // 字符类: [] 表示有一系列字符可供选择,只要匹配其中一个就可以了
    var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
    console.log(rg.test('andy'));
    console.log(rg.test('baby'));
    console.log(rg.test('color'));
    console.log(rg.test('red'));
    var rg1 = /^[abc]$/; // 三选一 只有是a 或者是 b  或者是c 这三个字母才返回 true
    console.log(rg1.test('aa'));
    console.log(rg1.test('a'));
    console.log(rg1.test('b'));
    console.log(rg1.test('c'));
    console.log(rg1.test('abc'));
    console.log('------------------');

    var reg = /^[a-z]$/; // 26个英文字母任何一个字母返回 true  - 表示的是a 到z 的范围  
    console.log(reg.test('a'));
    console.log(reg.test('z'));
    console.log(reg.test(1));
    console.log(reg.test('A'));
    // 字符组合
    var reg1 = /^[a-zA-Z0-9_-]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true  
    console.log(reg1.test('a'));
    console.log(reg1.test('B'));
    console.log(reg1.test(8));
    console.log(reg1.test('-'));
    console.log(reg1.test('_'));
    console.log(reg1.test('!'));
    console.log('----------------');
    // 如果中括号里面有^ 表示取反的意思 千万和 我们边界符 ^ 别混淆
    var reg2 = /^[^a-zA-Z0-9_-]$/;
    console.log(reg2.test('a'));
    console.log(reg2.test('B'));
    console.log(reg2.test(8));
    console.log(reg2.test('-'));
    console.log(reg2.test('_'));
    console.log(reg2.test('!'));
</script>

量词符

量词符用来设定某个模式出现的次数。 

量词         说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
<script>
    // 量词符: 用来设定某个模式出现的次数
    // 简单理解: 就是让下面的a这个字符重复多少次
    // var reg = /^a$/;


    //  * 相当于 >= 0 可以出现0次或者很多次 
    // var reg = /^a*$/;
    // console.log(reg.test(''));
    // console.log(reg.test('a'));
    // console.log(reg.test('aaaa'));



    //  + 相当于 >= 1 可以出现1次或者很多次
    // var reg = /^a+$/;
    // console.log(reg.test('')); // false
    // console.log(reg.test('a')); // true
    // console.log(reg.test('aaaa')); // true

    //  ?  相当于 1 || 0
    // var reg = /^a?$/;
    // console.log(reg.test('')); // true
    // console.log(reg.test('a')); // true
    // console.log(reg.test('aaaa')); // false

    //  {3 } 就是重复3次
    // var reg = /^a{3}$/;
    // console.log(reg.test('')); // false
    // console.log(reg.test('a')); // false
    // console.log(reg.test('aaaa')); // false
    // console.log(reg.test('aaa')); // true
    //  {3, }  大于等于3
    var reg = /^a{3,}$/;
    console.log(reg.test('')); // false
    console.log(reg.test('a')); // false
    console.log(reg.test('aaaa')); // true
    console.log(reg.test('aaa')); // true
    //  {3,16}  大于等于3 并且 小于等于16
    var reg = /^a{3,6}$/;
    console.log(reg.test('')); // false
    console.log(reg.test('a')); // false
    console.log(reg.test('aaaa')); // true
    console.log(reg.test('aaa')); // true
    console.log(reg.test('aaaaaaa')); // false
</script>

案例:用户名验证

功能需求:

1. 如果用户名输入合法, 则后面提示信息为 : 用户名合法,并且颜色为绿色

2. 如果用户名输入不合法, 则后面提示信息为: 用户名不符合规范, 并且颜色为绿色

分析:

1. 用户名只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为 6~16 位

2. 首先准备好这种正则表达式模式 /$[a-zA-Z0-9-_]{6,16}^/

3. 当表单失去焦点就开始验证

4. 如果符合正则规范, 则让后面的 span 标签添加 right 类

5. 如果不符合正则规范, 则让后面的 span 标签添加 wrong 类

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        span {
            color: #aaa;
            font-size: 14px;
        }
        
        .right {
            color: green;
        }
        
        .wrong {
            color: red;
        }
    </style>
</head>

<body>
    <input type="text" class="uname"> <span>请输入用户名</span>
    <script>
        //  量词是设定某个模式出现的次数
        var reg = /^[a-zA-Z0-9_-]{6,16}$/; // 这个模式用户只能输入英文字母 数字 下划线 短横线但是有边界符和[] 这就限定了只能多选1
        // {6,16}  中间不要有空格
        // console.log(reg.test('a'));
        // console.log(reg.test('8'));
        // console.log(reg.test('18'));
        // console.log(reg.test('aa'));
        // console.log('-------------');
        // console.log(reg.test('andy-red'));
        // console.log(reg.test('andy_red'));
        // console.log(reg.test('andy007'));
        // console.log(reg.test('andy!007'));
        var uname = document.querySelector('.uname');
        var span = document.querySelector('span');
        uname.onblur = function() {
            if (reg.test(this.value)) {
                console.log('正确的');
                span.className = 'right';
                span.innerHTML = '用户名格式输入正确';
            } else {
                console.log('错误的');
                span.className = 'wrong';
                span.innerHTML = '用户名格式输入不正确';
            }
        }
    </script>
</body>

</html>

括号总结

1. 大括号  量词符。里面表示重复次数

2. 中括号  字符集合。匹配方括号中的任意字符。

3. 小括号  表示优先级

可以在线测试:https://c.runoob.com/

<script>
    // 中括号 字符集合.匹配方括号中的任意字符. 
    // var reg = /^[abc]$/;
    // a 也可以 b 也可以 c 可以  a ||b || c
    // 大括号  量词符. 里面表示重复次数
    // var reg = /^abc{3}$/; // 它只是让c重复三次   abccc
    // console.log(reg.test('abc'));
    // console.log(reg.test('abcabcabc'));
    // console.log(reg.test('abccc'));

    // 小括号 表示优先级
    var reg = /^(abc){3}$/; // 它是让abcc重复三次
    console.log(reg.test('abc'));
    console.log(reg.test('abcabcabc'));
    console.log(reg.test('abccc'));
</script>

预定义类

预定义类指的是某些常见模式的简写方式。 

预定类 说明
\d 匹配0-9之间的任一数字。相当于[0-9]
\D 匹配0-9以外的字符。相当于[^0-9]
\w 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
\W 除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]
\s 匹配空格(包括换行符、制表符、空格符等),相当于[\t\r\n\v\f]
\S 匹配非空格字符,相当于[^\t\r\n\v\f]

案例:座机号验证

<script>
    // 座机号码验证:  全国座机号码  两种格式:   010-12345678  或者  0530-1234567
    // 正则里面的或者 符号  |  
    // var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/;
    var reg = /^\d{3,4}-\d{7,8}$/;
</script>

05 - 正则表达式中的替换

replace 替换

replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。 

stringObject.replace(regexp/substr,replacement)
  • 第一个参数:被替换的字符串 或者 正则表达式
  • 第二个参数:替换为的字符串
  • 返回值是一个替换完毕的新字符串

正则表达式参数

/表达式/[switch]

 switch (也称为修饰符) 按照什么样的模式来匹配。有三种值:

● g:全局匹配

● i:忽略大小写

● gi:全局匹配 + 忽略大小写

案例:敏感词过滤

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        textarea {
            width: 300px;
            height: 100px;
            border: 1px solid #ccc;
        }
    </style>
</head>

<body>
    <textarea name="" id="message"></textarea> <button>提交</button>
    <div></div>
    <script>
        // 替换 replace
        // var str = 'andy和red';
        // // var newStr = str.replace('andy', 'baby');
        // var newStr = str.replace(/andy/, 'baby');
        // console.log(newStr);
        var text = document.querySelector('textarea');
        var btn = document.querySelector('button');
        var div = document.querySelector('div');
        btn.onclick = function() {
            div.innerHTML = text.value.replace(/激情|gay/g, '**');
        }
    </script>
</body>

</html>

Day 5 - ES6 部分知识 

01 - 学习目标(target )

  • 能够说出使用 let 关键字声明变量的特点
  • 能够使用解构赋值从数组中提取值
  • 能够说出箭头函数拥有的特性
  • 能够使用剩余参数接收剩余的函数参数
  • 能够使用拓展运算符拆分数组
  • 能够说出模板字符串拥有的特性

02 - ES6简介

什么是 ES6

ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 

 ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。

为什么使用 ES6

每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript 语言本身也有一些令人不满意的地方。

  • 变量提升特性增加了程序运行时的不可预测性
  • 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码

03 - ES6 的新增语法

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;

● 暂时性死区

var tmp = 123;
  if (true) {
    tmp = 'abc';
    let tmp;
}
<script type="text/javascript">
/*
	let关键字就是用来声明变量的

	使用let关键字声明的变量具有块级作用域

	在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的

	防止循环变量变成全局变量

	使用let关键字声明的变量没有变量提升

	使用let关键字声明的变量具有暂时性死区特性

*/
		
/* --------let关键字就是用来声明变量的-------- */
	// let a = 10;
	// console.log(a);
		
/* --------使用let关键字声明的变量具有块级作用域-------- */
	// if (true) {
	// 	let b = 20;
	// 	console.log(b)
	// 	if (true) {
	// 		let c = 30;
	// 	}
	// 	console.log(c);
	// }
	// console.log(b)
		
/* -------在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的--------- */

	// if (true) {
	// 	let num = 100;
	// 	var abc = 200;
	// }
	// console.log(abc);
	// console.log(num)


/* -------防止循环变量变成全局变量--------- */
	// for (let i = 0; i < 2; i++) {}
	// console.log(i);
		

/*-----使用let关键字声明的变量没有变量提升------*/
	// console.log(a);
	// let a = 100;
		

/* -------使用let关键字声明的变量具有暂时性死区特性------- */
	var num = 10
	if (true) {
		console.log(num);
		let num = 20;
	}

</script>

经典面试题

经典面试题图解:此题的关键点在于变量 i 是全局的,函数执行时输出的都是全局作用域下的值。

 

经典面试题图解:此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的 i 值

<script type="text/javascript">
	let arr = [];

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

	arr[0]();
	arr[1]();
</script>

const 关键字

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

● 具有块级作用域 

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

● 声明常量时必须赋值

const PI; // Missing initializer in const declaration

● 常量赋值后,值不能修改。

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.
<script type="text/javascript">
	// 使用const关键字声明的常量具有块级作用域
	// if (true) {
	// 	const a = 10;
	// 	if (true) {
	// 		const a = 20;
	// 		console.log(a);
	// 	}
	// 	console.log(a);
	// }
	// console.log(a);
		
	// 使用const关键字声明的常量必须赋初始值
	// const PI = 3.14;
		
	// 常量声明后值不可更改 
	const PI = 3.14;
	// PI = 100;
	const ary = [100, 200];
	ary[0] = 123;
	ary = [1, 2]
	console.log(ary);
</script>

let、const、var 的区别

1. 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。

2. 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。

3. 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值。 

解构赋值

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

数组解构 

let [a, b, c] = [1, 2, 3];
console.log(a)
console.log(b)
console.log(c)

如果解构不成功,变量的值为 undefined。

let [foo] = [];
let [bar, foo] = [1];
<script type="text/javascript">
	// 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
	let ary = [1,2,3];
	let [a, b, c, d, e] = ary;
	console.log(a)
	console.log(b)
	console.log(c)
	console.log(d)
	console.log(e)
</script>

对象解构

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
<script type="text/javascript">
	// 对象解构允许我们使用变量的名字匹配对象的属性 匹配成功 将对象属性的值赋值给变量
		
	let person = {name: 'lisi', age: 30, sex: '男'};
	// let { name, age, sex } = person;
	// console.log(name)
	// console.log(age)
	// console.log(sex)
		
	let {name: myName} = person;
	console.log(myName)

</script>

箭头函数

ES6中新增的定义函数的方式。

() => {} 
const fn = () => {}

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

function sum(num1, num2) {
    return num1 + num2;
}
const sum = (num1, num2) => num1 + num2;

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

function fn (v) {
    return v;
}
const fn = v => v;

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

const obj = { name: '张三'}
function fn () {
    console.log(this);
    return () => {
      console.log(this)
    }
}
const resFn = fn.call(obj);
resFn();
<script type="text/javascript">
	// 箭头函数是用来简化函数定义语法的
	// const fn = () => {
	// 	console.log(123)
	// }
	// fn();
		
// 在箭头函数中 如果函数体中只有一句代码 并且代码的执行结果就是函数的返回值 函数体大括号可以省略
	// const sum = (n1, n2) => n1 + n2;	 
	// const result = sum(10, 20);
	// console.log(result)
		
	// 在箭头函数中 如果形参只有一个 形参外侧的小括号也是可以省略的
	// const fn = v => {
	// 	alert(v);
	// }
	// fn(20)
		
// 箭头函数不绑定this 箭头函数没有自己的this关键字 如果在箭头函数中使用this this关键字将指向箭头函数定义位置中的this
		
	function fn () {
		console.log(this);
		return () => {
			console.log(this)
		}
	}

	const obj = {name: 'zhangsan'};

	const resFn = fn.call(obj);

	resFn();
</script>

箭头函数面试题        

<script type="text/javascript">
	
	var age = 100;

	var obj = {
		age: 20,
		say: () => {
			alert(this.age)
		}
	}
    obj.say();
</script>

剩余参数

剩余参数语法允许我们将一个不定数量的参数表示为一个数组。

function sum (first, ...args) {
    console.log(first); // 10
    console.log(args); // [20, 30] 
}
sum(10, 20, 30)

 剩余参数和解构配合使用

let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu' 
console.log(s2); // ['zhangsan', 'lisi']
<script type="text/javascript">
	// const sum = (...args) => {
	// 	let total = 0;
	// 	args.forEach(item => total += item);
	// 	return total;
	// };

	// console.log(sum(10, 20));
	// console.log(sum(10, 20, 30));
		

	let ary1 = ['张三' , '李四', '王五'];
	let [s1, ...s2] = ary1;
	console.log(s1)
	console.log(s2)

</script>

04 - ES6 的内置对象扩展

Array 的扩展方法 

扩展运算符(展开语法) 

扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。

let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3
console.log(1, 2, 3)

扩展运算符可以应用于合并数组。

// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);

将类数组或可遍历对象转换为真正的数组

let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>扩展运算符</title>
</head>
<body>
	<div>1</div>
	<div>4</div>
	<div>3</div>
	<div>6</div>
	<div>2</div>
	<div>5</div>
	<script type="text/javascript">
		// 扩展运算符可以将数组拆分成以逗号分隔的参数序列
		// let ary = ["a", "b", "c"];
		// ...ary // "a", "b", "c"
		// console.log(...ary)
		// console.log("a", "b", "c")
		
		// 扩展运算符应用于数组合并
		// let ary1 = [1, 2, 3];
		// let ary2 = [4, 5, 6];
		// // ...ary1 // 1, 2, 3
		// // ...ary1 // 4, 5, 6
		// let ary3 = [...ary1, ...ary2];
		// console.log(ary3)

		// 合并数组的第二种方法
		// let ary1 = [1, 2, 3];
		// let ary2 = [4, 5, 6];

		// ary1.push(...ary2);
		// console.log(ary1)
		
		// 利用扩展运算符将伪数组转换为真正的数组
		var oDivs = document.getElementsByTagName('div');
		console.log(oDivs)
		var ary = [...oDivs];
		ary.push('a');
		console.log(ary);
	</script>
</body>
</html>

构造函数方法:Array.from()

将类数组或可遍历对象转换为真正的数组

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

方法还可以接受第二个参数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

let arrayLike = {
    "0": 1,
    "1": 2,
    "length": 2
}
let newAry = Array.from(aryLike, item => item *2)
<script type="text/javascript">
	// var arrayLike = {
	// 	"0": "张三",
	// 	"1": "李四",
	// 	"2": "王五",
	// 	"length": 3
	// }

	// var ary = Array.from(arrayLike);
	// console.log(ary)
		
	var arrayLike = {
		"0": "1",
		"1": "2",
		"length": 2
	}

	var ary = Array.from(arrayLike, item => item * 2)
	console.log(ary)
</script>

实例方法:find()

用于找出第一个符合条件的数组成员,如果没有找到返回 undefined。

let ary = [{
    id: 1,
    name: '张三‘
}, {
    id: 2,
    name: '李四‘
}];
let target = ary.find((item, index) => item.id == 2)
<script type="text/javascript">
	var ary = [{
		id: 1,
		name: '张三'
	}, {
		id: 2,
		name: '李四'
	}];
	let target = ary.find(item => item.id == 3);
	console.log(target)
</script>

实例方法:findIndex() 

 用于找出第一个符合条件的数组成员的位置,如果没有找到返回 -1

let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
<script type="text/javascript">
	let ary = [10, 20, 50];
	let index = ary.findIndex(item => item > 15);
	console.log(index)
</script>

实例方法:includes()

表示某个数组是否包含给定的值,返回布尔值。

[1, 2, 3].includes(2) // true 
[1, 2, 3].includes(4) // false
<script type="text/javascript">
	let ary = ["a", "b", "c"];

	let result = ary.includes('a')
	console.log(result)
	result = ary.includes('e')
	console.log(result)
</script>

String 的扩展方法

模板字符串 

ES6 新增的创建字符串的方式,使用反引号定义。

let name = `zhangsan`;

 模板字符串中可以解析变量。

let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan

模板字符串中可以换行

let result = {
    name: 'zhangsan',
    age: 20,
    sex: '男'
}
let html = ` <div>
    <span>${result.name}</span>
    <span>${result.age}</span>
    <span>${result.sex}</span>
</div> `;

在模板字符串中可以调用函数

const sayHello = function () {
    return '哈哈哈哈 追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 哈哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈
<script type="text/javascript">
	// let name = `张三`;
	// let sayHello = `Hello, 我的名字叫${name}`;
	// console.log(sayHello);
		
	// let result = {
	// 	name: "zhangsan",
	// 	age: 20
	// };
	// let html = `
	// 	<div>
	// 		<span>${result.name}</span>
	// 		<span>${result.age}</span>
	// 	</div>
	// `;
	// console.log(html);
	
	const fn = () => {
		return '我是fn函数'
	}

	let html = `我是模板字符串 ${fn()}`;
	console.log(html)

</script>

实例方法:startsWith() 和 endsWith()

● startsWith():表示参数字符串是否在原字符串的头部,返回布尔值

● endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值 

let str = 'Hello world!';
str.startsWith('Hello') // true 
str.endsWith('!') // true
<script type="text/javascript">
	let str = 'Hello ECMAScript 2015';
	let r1 = str.startsWith('Hello');
	console.log(r1);
	let r2 = str.endsWith('2016');
	console.log(r2)
</script>

实例方法:repeat()

repeat 方法表示将原字符串重复 n 次,返回一个新字符串。

'x'.repeat(3) // "xxx" 
'hello'.repeat(2) // "hellohello"
<script type="text/javascript">
	console.log("y".repeat(5))
</script>

Set 数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set 本身是一个构造函数,用来生成 Set 数据结构。

const s = new Set();

 Set 函数可以接受一个数组作为参数,用来初始化。

const set = new Set([1, 2, 3, 4, 4]);

实例方法

● add(value):添加某个值,返回 Set 结构本身

● delete(value):删除某个值,返回一个布尔值,表示删除是否成功

● has(value):返回一个布尔值,表示该值是否为 Set 的成员

● clear():清除所有成员,没有返回值 

const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值
s.delete(2) // 删除 set 结构中的2值
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值

遍历

Set 结构的实例与数组一样,也拥有 forEach 方法,用于对每个成员执行某种操作,没有返回值。

s.forEach(value => console.log(value))

<script type="text/javascript">
	// const s1 = new Set();
	// console.log(s1.size)

	// const s2 = new Set(["a", "b"]);
	// console.log(s2.size)

	// const s3 = new Set(["a","a","b","b"]);
	// console.log(s3.size)
	// const ary = [...s3];
	// console.log(ary)
		
	// const s4 = new Set();
	// 向set结构中添加值 使用add方法
	// s4.add('a').add('b');
	// console.log(s4.size)

	// 从set结构中删除值 用到的方法是delete
	// const r1 = s4.delete('c');
	// console.log(s4.size)
	// console.log(r1);

	// 判断某一个值是否是set数据结构中的成员 使用has
	// const r2 = s4.has('d');
	// console.log(r2)

	// 清空set数据结构中的值 使用clear方法
	// s4.clear();
	// console.log(s4.size);
		
	// 遍历set数据结构 从中取值
	const s5 = new Set(['a', 'b', 'c']);
	s5.forEach(value => {
		console.log(value)
	})

</script>

到这儿就结束了,本文主要包含B站上面黑马前端教程关于ES6 部分的 PPT 和实例代码,希望对大家有所帮助,若文中有错误,欢迎在评论区下方指出,谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_44566194/article/details/127568441
今日推荐