JavaScrip提高——面向对象和函数进阶

150 阅读28分钟

1. 面向对象

面向对象更贴近我们的实际生活,可以使用面向对象描述现实世界事物。事物分为具体的事物和抽象的事物。

面向对象的思维特点:

  1. 抽取(抽象)共用的属性和行为组织(封装)成一个类(模板)
  2. 对类实例化,获取类的对象

面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点,不断地创建对象,使用对象,只会对象做事情。

1.1 JS对象

对象是一个具体的事物,万物皆对象。

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

对象由属性方法组成:

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

1.2 类

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

  • 抽象了对象的公共部分,它泛指某一大类(class)
  • 对象特指某一个,通过类实例化一个具体的对象

1.2.1 创建类和创建实例

//创建类
class name {
    //class body
}
//创建实例
var XX = new name();

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

1.2.2 构造函数

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

  // 创建类class
        class Star{
        //构造函数
            constructor(uname,age){
                this.uname=uname;
                this.age=age;
            }
        }

        // 利用类创建对象
        var mo=new Star('mona',20);
        console.log(mo);
  • 通过class关键字创建类,类名习惯性定义首字母大写

  • constructor函数,可以接收传递过来的参数,同时返回实例对象

  • constructor函数只要 new 生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数

  • 最后注意语法规范

    • 创建类➡类名后面不要加小括号
    • 生成实例➡类名后面加小括号
    • 构造函数不需要加 function 关键字

1.2.3 类中添加方法

  // 创建类class
        class Star{
        //构造函数
            constructor(uname,age){
                this.uname=uname;
                this.age=age;
            }   
            say() {
                console.log(this.name+'hello')
            };
            sing(song) {
                console.log(song)
            }
        }

        // 利用类创建对象
        var mo=new Star('mona',20);
        console.log(mo);
        mo.say();
        mo.sing('这里有一首儿歌')
  • 方法之间不能加逗号分隔,同时方法也不需要加function关键字
  • 类的共有属性放到constructor里面

1.3 类的继承

  • 子类继承父类的一些属性和方法。
// 父类
class Father {
    
}
// 子类继承父类
class Son extends Father {
    
}
<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.4 super关键字

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

1.4.1 调用父类的构造函数

// 父类
class Person {
    constructor(surname){
        this.surname = surname;
    }
}
// 子类继承父类
class Student entends Person {
    constructor(surname,firstname) {
        super(surname);         //调用父类的构造函数,super必须写在子类this之前
        this.firstname = firstname;  //定义子类独有的属性
    }
}
  • 子类在构造函数中使用super,必须写在子类this之前

1.4.2 调用父类的普通函数

class Father {
    say() {
        return '我是父类';
    }
}
class Son extends Father {
    say(){
        // super.say() super调用父类的方法
        return super.say() + '的孩子';
    }
}

var son = new Son();
console.log(son.say());
  • 继承中属性和方法的查找原则:就近原则,先看子类,再看父类

1.4.3 三个注意点

  1. ES6中,必须先定义类,才能通过类实例化对象
  2. 类中的共有属性和方法一定要加this使用
  3. 类中的this指向:
    • constructor里的this指向实例对象
    • 方法里的this指向这个方法的调用者
<body>
    <button>点击</button>
    <script>
        var that, _that;
        class Star {
            constructor(uname, age) {
                //constructor里的this指向创建的实例对象
                that = this;
                this.uname = uname;
                this.age = age;
                this.btn = document.querySelector('button');
                this.btn.onclick = this.sing;
            }

            sing() {
                console.log(that.uname);
            }

            dance() {
                // 这里的this指向实例对象ldh,因为ldh调用了这个函数
                _that = this;
                console.log(this);
            } 
        }

        var mo = new Star('mona');
        console.log(that === mo);
        mo.dance();
        console.log(_that === mo);

        // constructor里面的this指向实例对象
        // 方法里的this指向这个方法的调用者
    </script>
</body>

2. 构造函数和原型

在典型的面向对象的语言中(如java),都存在类的概念。类是对象的模板,对象就是类的实例,但是在ES6之前,JS中没有引入类的概念。

ES6,全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。

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


为什么需要对象?

保存一个值时,可以使用变量,保存多个值(一组值)时,可以使用数组。如果要保存一个人的完整信息呢?

  • 个人信息保存在数组时:
var arr=['mona','男',18,163]
  • 个人信息保存在对象时:
person.name='mona';
person.sex='男';
person.age=18;
person.height=163;

JS中的对象表达结构更清晰、更强大。

2.1 创建对象的三种方法

在js中,现阶段可以采用三种方式创建对象:

  • 利用字面量创建对象
  • 利用new Object创建对象
  • 利用构造函数创建对象

1.利用字面量创建对象

对象字面量就是花括号{ } 里面包含了表达这个具体事务(对象)的属性和方法

//利用对象字面量创建对象
//var obj={};//创建了一个空对象
var obj={
    uname:'mona',
    sex:'男',
    age:18,
    height:163,
    sayHi:function(){
        console.log('hi~');
    }
}
  • 里面的属性或方法采用键值对方法表示
  • 多个属性或者方法中间用逗号隔开
  • 方法冒号后面跟的是一个匿名函数
//使用对象

//调用对象的属性
//方法1:对象名.属性名
console.log(obj.uname);
//方法2:对象名['属性名']
console.log(obj['uname']);

//调用对象的方法:对象名.方法名()
obj.sayHi();

2. 利用new Object创建对象

和new Array()原理一样

var obj=new Object();//创建一个空的对象
obj.uname='mona';
obj.sex='男';
obj.age=18;
obj.height=163;
obj.sayHi=function(){
    console.log('hi~');
}

console.log(obj.uname);
console.log(obj['uname']);
obj.sayHi();
  • 利用 等号= 赋值的方法 添加对象的属性和方法
  • 每个属性和方法之间用分号结束

3. 利用构造函数创建对象

为什么要使用构造函数创建对象?

  • 因为前俩的创建方式一次只能创建一个对象,里面很多的属性和方法是大量相同的,要创建多个的话只能复制
//构造函数的语法格式
function 构造函数名(){
    this.属性=值;
    this.方法=function(){}
}

new 构造函数名();//构造函数的调用方法
function Students(uname,age,sex){
    this.uname=uname;
    this.age=age;
    this.sex=sex;
    this.read=function(readbook){
        console.log(readbook);
    }
}

var wang=new Students('mona',18,'男');//调用函数返回一个对象
console.log(typeof wang);//object
console.log(wang.uname);//mona
console.log(wang['sex']);//男
wang.read('时间简史');
  • 构造函数名字首字母要大写
  • 构造函数不需要return就可以返回结果
  • 调用构造函数的时候 必须使用new
  • 我们只要调用一次函数就创建一个对象
  • 属性和方法前必须加this

4. 遍历对象

下面这方法...真的是很麻烦!!!

var obj={
name:'mona',
age:18,
sex:'女'
}

console.log(obj.name);
console.log(obj.age);
console.log(obj.sex);


对象里的内容是无序的,传统for循环也做不到遍历它。所以!!
for...in语句 用于对数组或者对象的属性进行循环操作

var obj={
    name:'mona',
    age:18,
    sex:'女',
    fn:function(){}
}

//for in遍历我们的对象
//for(变量 in 对象){ }

for(var k in obj){
    console.log(k);//k 变量 输出得到的是属性名
    console.log(obj[k]);// obj[k] 得到的是属性值
}

2.2 构造函数

  • 构造函数:是一种特殊的函数,主要用来初始化对象。即对象成员变量赋初始值,它总与new运算符一起使用。
  • 我们把对象里相同的属性和方法抽象出来封装到这个函数里(就是说这个函数里封装的是对象

new关键字执行过程:

  1. 构造函数遇到new时,就在内存中创建一个空的对象
  2. 构造函数里的this指向空对象
  3. 执行构造函数里的代码,给空对象添加属性和方法
  4. 返回这个对象(所以构造函数里不需要return)

2.3 构造函数和对象的相互关系

  • 构造函数,如Students(),抽象了对象的公共部分,封装到了函数里面,它泛指某一大类class
  • 创造对象,如new Students(),特指某一个,通过new关键字创建对象的过程我们也称为对象实例化
//构造函数 学生 泛指某一大类
function Students(uname,age,sex){
    this.uname=uname;
    this.age=age;
    this.sex=sex;
    this.read=function(readbook){
        console.log(readbook);
    }
}
//对象 一个具体的事物
var wang=new Students('mona',18,'男');
console.log(wang);

2.4 构造函数的静态成员和实例成员

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

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

        // 实例化对象
        var ldh=new Star('liudehua',18);


        // 实例成员就是构造函数内部通过this添加成员 uname、age、sing就是实例成员
        // 实例成员只能通过实例化对象来访问
        ldh.sing();
        console.log(ldh.uname);
        console.log(Star.uname);;//undefined 不可以通过构造函数来访问实例成员
        
        // 静态成员就是在构造函数身上 添加成员  sex就是静态成员
        // 静态成员只能通过构造函数来访问
        Star.sex='man';
        console.log(Star.sex);
        console.log(ldh.sex);//undefined 不能通过对象来访问
    </script>

2.5 构造函数的问题

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

function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = function () {
    console.log('hello ' + this.name)
  }
}
 
var p1 = new Person('Tom', 18)
var p2 = new Person('Jack', 16)

对于每一个实例对象,都会开辟一个新的内存空间,但是type和sayHello都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,若实例对象很多的话,就会造成极大地内存浪费。

  • 我们希望所有的对象使用同一个函数,这样比较节省内存。

2.6 构造函数原型

  • 构造函数通过原型分配的函数是所有对象所共享的,这样就解决了内存浪费的问题
  • JavaScript 规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
  • 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
<body>
    <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);
        ldh.sing();
        zxy.sing();
        // 2. 一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
       
        // 原型是什么?  一个对象,我们也称为prototype为原型对象
        // 原型的作用是什么?  共享方法
    </script>
</body>

2.7 对象原型__proto__

  • 对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在
  • __proto__对象原型和原型对象prototype是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

image.png

  • Star.prototype ldh.__proto__指向相同
     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这个方法

            //console.log(ldh.__proto__.constructor);

2.8 constructor构造函数

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

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

  • 一般情况下,对象的方法都在构造函数prototype的原型对象中设置

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

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

        // Star.prototype.sing=function(){
        //     console.log('sing~');
        // }

        // Star.prototype.movie=function(){
        //     console.log('movie~');
        // }

        /* 如果有多个对象的方法,我们可以给原型对象prototype采取对象形式赋值,
        但这样会覆盖掉构造函数原型对象原来的内容,这样修改过后原型对象不指向当前构造函数了。
        我们可以手动在修改后的原型对象中加一个 constructor:Star 指向原来的构造函数*/
        Star.prototype = {
            constroctor:Star,
            sing: function () {
                console.log('sing~');
            },
            movie: function () {
                console.log('movie~');
            }
        }

            var ldh = new Star('刘德华', 18);
            var zxy = new Star('张学友', 19);

            console.log(Star.prototype.constroctor);
            console.log(ldh.__proto__.constroctor);

2.9 构造函数、实例、原型对象三者关系

image.png

2.10 原型链查找规则

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  2. 如果没有就查找它的原型(也就是_proto_指向的prototype原型对象)
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 依次类推一直找到Object为止(null)
  5. __ proto __对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

image.png

<body>
    <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>
</body>

2.11 原型对象this指向

  • 构造函数中的this指向我们的实例对象
  • 原型对象里放的是方法,这个方法里面的this指向的是这个方法的调用者,也就是这个实例对象
<body>
    <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>
</body>

3. 继承

ES6之前并没有给我们提供extends继承

  • 我们可以通过构造函数+原型对象模拟实现继承,称为组合继承

3.1 call()

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

fun.call(thisArg,arg1,arg2,...)
  • thisArg: 当前调用函数this的指向对象
  • arg1,arg2:传递的其他参数
<body>
    <script>
        // call 方法
        function fn(x, y) {
            console.log('好好学习');
            console.log(this);		// Object{...} 指向window
            console.log(x + y);		// 3
        }

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

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

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

<body>
    <script>
        // 借用父构造函数继承属性
        // 1. 父构造函数
        function Father(uname, age) {
            // this 指向父构造函数的对象实例
            this.uname = uname;
            this.age = age;
        }
        // 2 .子构造函数 
        function Son(uname, age, score) {
            // this 指向子构造函数的对象实例
            //调用父构造函数,并且把父构造函数的this改成子构造函数的this
            //这句代码写到子构造函数中,子构造函数中的this指向子构造函数的对象实例
            Father.call(this, uname, age);
            this.score = score;
        }
        var son = new Son('刘德华', 18, 100);//结果既具有父亲的属性,又有自己的属性
        console.log(son);
    </script>
</body>

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

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

核心原理:

  1. 将子类所共享的方法提取出来,让子类的protopyte原型对象 = new 父类()
  2. 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
  3. 将子类的constructor重新指向之类的构造函数
<body>
    <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();//new的实例对象和原型对象在两个不同的空间中
        // 如果利用对象的形式修改了原型对象,别忘了利用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>
</body>

image.png

3.4 ES6的类的本质

  • ES6之前通过 构造函数+原型 实现面向对象编程
  1. 构造函数有原型对象prototype
  2. 构造函数原型对象prototype里面有constructor指向构造函数本身
  3. 构造函数可以通过原型对象添加方法
  4. 构造函数的实例对象有__proto__原型指向 构造函数的原型对象
  • ES6通过 实现面向对象编程
  1. 类(class)的本质还是一个函数(function),可以简单的认为类就是构造函数的另外一种写法
  2. 类的所有方法都定义在类的prototype属性上
  3. 类创建的实例,里面也有__proto__指向类的prototype原型对象
  4. ES6的大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰,更偏向面向对象编程的语法
  5. 所以ES6的类就是语法糖(就是一种便捷的写法,比如两种方法实现同一种功能,但是有一种更加清晰方便,那么这个方法就是语法糖)
class Star {

        }
        console.log(typeof Star);//function
        // 1.类的本质其实还是一个函数 ,类就是构造函数的另外一种写法
        // 1. 类有原型对象`prototype`
        console.log(Star.prototype);//里面的constructor指向Star
        // 2. 类`原型对象prototype`里面有`constructor`指向类本身
        console.log(Star.prototype.constructor);
        // 3.类可以通过原型对象添加方法
        Star.prototype.sing = function () {
            console.log('好运来');
        }
        var mona = new Star();
        console.log(mona);
        // 4.类创建实例对象由__proto__原型指向 类的原型对象
        console.log(mona.__proto__ === Star.prototype);//true

4.ES5中新增方法

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

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

4.1 数组方法

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

4.1.1 forEach

array.forEach(function(currentValue,index,arr))
  • currentValue: 数组当前项的值
  • index: 数组当前项的索引
  • arr: 数组对象本身
//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);

4.1.2 filter()

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

4.1.3 some

arr.some(function(currentValue,index,arr))
  • some()方法用于检测数组中的元素是否满足指定条件,就是查找数组中是否有满足条件的元素
  • 注意返回的是布尔值,如果能查找到这个元素就返回true,找不到就返回false
  • 如果找到第一个满足条件的元素,就停止循环,不再继续查找
  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
 // some 查找数组中是否有满足条件的元素
        var arr=[20,20,30];
        var flag=arr.some(function(value){
            //用什么参数写什么参数即可
            return value>=20;
        });
        console.log(flag);//true

        // 直接查找某个元素
        var arr1=['red','pink','blue'];
        var flag1=arr1.some(function(value){
            return value='pink';
        });
        console.log(flag1);//true   
  • filter 是查找满足条件的元素,返回的是一个新数组(返回的是所有满足条件的元素)
  • some 是查找满足条件的元素是否存在 返回的是一个布尔值,找到第一个满足条件的元素就终止循环
        var arr = ['red', 'pink', 'blue'];
        // arr.forEach(function (value) {
        //     if (value == 'pink') {
        //         console.log('找到了');
        //         return true;  //forEach 和 filter 遇到 return true 不会停迭代
        //     }
        //     console.log('11');
        // })

        arr.some(function (value) {
            if (value == 'pink') {
                console.log('找到了');
                return true;//在some遇到 return true 终止循环 迭代效率高
            }
            console.log('11');
        })

4.2 字符串方法

trim()方法会从一个字符串的两端删除空白字符,但是如果在字符串中间有空格,不会删除字符串中间的空格

str.trim()
  • trim()方法不会影响字符串本身,他返回的是一个新的字符串
<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>

这个方法可以解决验证表单的小bug

4.3 对象方法

4.3.1 Object.keys()

  1. Object.keys()用于获取对象所有的属性,返回值是由属性名组成的数组
  2. 效果类似于for...in
<body>
    <script>
        // 用于获取对象自身所有的属性
        var obj = {
            id: 1,
            pname: '小米',
            price: 1999,
            num: 2000
        };
        //新增属性
        obj.company='xiaomi';
        
        var arr = Object.keys(obj);
        console.log(arr);//数组形式输出所有属性
        //forEach迭代数组
        arr.forEach(function(value) {
            console.log(value);
            // id
            // pname
            // price
            // num
        })
    </script>
</body>

4.3.2 Object.defineProperty()

定义对象中新属性或修改原有的属性

Object.defineProperty(obj,prop,descriptor)
  • obj:必需。目标对象
  • prop:必需。需定义或修改的属性的名字(以字符串形式书写)
  • descriptor:必需。目标属性所拥有的特性(以对象形式{}书写)
    • value:设置属性的值 默认为undefined
    • writable:值是否可以重写,true | false 默认为false
    • enumerable:目标属性是否可以被枚举,true | false 默认为false
    • configurable:目标属性是否可以被删除或是否可以再次修改特性,true | false 默认为false
var obj = {
            id: 1,
            pname: '小米',
            price: 1999,
            num: 2000
        };

        Object.defineProperty(obj, 'num', {
            value: 1000
            //等价于obj.num=1000  没有该属性就是添加,有就是修改
        });

        Object.defineProperty(obj, 'id', {
            writable: false,//默认 false不允许修改
        });
        obj.id = 2;//id还是1  

        Object.defineProperty(obj, 'address', {
            value: '中国山东',
            writable: false,
            enumerable: false,//默认 false 不允许遍历
            configurable: false,//默认false 不允许删除
        });

        console.log(obj);
        console.log(Object.keys(obj));

        delete obj.address;
        console.log(obj);

        Object.defineProperty(obj, 'address', {
            value: '中国山东',
            writable: true,
            enumerable: true,//默认 false 不允许遍历
            configurable: true,//默认false 不允许删除 不允许修改第三个参数里面的特性
        });
        console.log(obj.address);//Cannot redefine property: adress

5. 函数进阶

5.1 函数的定义方式

  1. 函数声明方式 function关键字(命名函数)
function fn() {};
  1. 函数声明方式 函数表达式(匿名函数)
var fun = function() {};
  1. new Function()(Function首字母大写了! 构造函数)
new Function('参数1','参数2','函数体')  

所有的函数都是Function的实例(对象)

//var f = new Function()

var f= new Function('a','b','console.log(a+b)');//参数都是字符串换形式
f(1,2)//3
//这种方式执行效率低,且不方便书写,因此很少使用 

console.dir(f); //所有的函数都是Function的实例(对象)
console.log(f instanceof Object); //函数也属于对象
//instanceof 检测前者属不属于后者

image.png

5.2 函数的调用方式

        // 函数的调用方式
        // 1、普通函数
        function fn() {
            console.log('好好学习');
        }
        fn();
        fn.call();

        // 2、对象的方法
        var o = {
            sayHello: function () {
                console.log('天天向上');
            }
        }
        o.sayHello();

        // 3、构造函数
        function Star(){};
        new Star();
        
        // 4、绑定事件函数
        btn.onclick=function(){};
        //点击了按钮即可调用

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

        // 6、立即执行函数
        (function(){
            console.log('早点睡觉');
        })()
        // 立即执行函数是自动调用

5.3 函数内的this指向

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

调用方式this指向
普通函数调用window
构造函数调用实例对象 原型对象里的方法也指向实例对象
对象方法调用该方法所属对象
事件绑定方法绑定事件对象
定时器函数window
立即执行函数window

5.4 改变函数内的this指向

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

5.4.1 call方法

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

fun.call(thisArg,ag1,arg2,...)+
  • thisArg: 当前调用函数this的指向对象

  • arg1,arg2:传递的其他参数

  • 返回值就是函数的返回值,因为它就是调用函数

  • 因此当我们想要改变this指向,同时想调用这个函数的时候,可以使用call

 // 改变函数的this指向
        var o = {
            name: 'mona',
        }
        function fn(a, b) {
            console.log(this);//o
            console.log(a + b);//3
        }
        fn.call(o, 1, 2);//不加o指向window,加o指向o

        // 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)//调用father的构造函数,并把father的this改为son的this
            console.log(this);
        } 
        var son = new Son('mona', 19, '女')

5.4.2 apply方法

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

fun.apply(thisArg,[argsArray])
  • thisArg: 在fun函数运行时指定的this指向
  • argsArray: 传递的值,必须包含在数组(伪数组)里面
  • 返回值就是函数的返回值,因为它就是调用函数
     var o = {
            name: 'mona',
        };
        function fn(arr) {
            console.log(this);
            console.log(arr);//'pink'
        };
        
        fn.apply(o, ['pink']);
        // 1. 是调用函数,可以改变函数内部的this指向
        // 2. 这个函数的参数必须是数组
        // apply的主要应用 比如说我们可以利用apply借助于数学内置对象求最大值
        var arr = [1, 66, 3, 99, 4];
        // console.log(Math.max.apply(null,arr)); //99
        var max = Math.max.apply(Math, arr);
        var min = Math.min.apply(Math, arr);
        console.log(max,min); //99 1

5.4.3 bind方法

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

fun.bind(thisArg,arg1,arg2,...)
  • thisArg: 在fun函数运行时指定的this值
  • arg1,arg2: 传递的其他参数
  • 返回由指定的this值和初始化参数改造的原函数拷贝
<body>
    <button>禁用按钮0</button>
    <button>禁用按钮1</button>
    <button>禁用按钮2</button>
    <button>禁用按钮3</button>
    <script>
        // 3.bind方法
        var o = {
            name: 'mona',
        };
        function fn(a, b) {
            console.log(this);
            console.log(a + b);
        };
        // fn.bind(o);//没有结果,不会调用函数
        var f = fn.bind(o, 1, 2);//指向o
        f();
        // 可以改变this指向,返回原函数改变this之后产生的新函数

        // 如果有函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用bind
        // 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒之后在开启这个按钮
        // var btn = document.querySelector('button');
        // btn.addEventListener('click', function () {
        //     this.disabled = true;//this 指向btn
        //     setTimeout(function () {
        //         // this.disabled=false;//定时器函数里的this指向window对象
        //         this.disabled = false;//此时定时器函数里的this指向btn对象
        //     }.bind(this), 3000)//this在定时器函数外面,指向btn对象
        //     //不用立即调用,但要改变指向,bind(this)写到定时器函数外面
        // })

        // 多个按钮
        var btns = document.querySelectorAll('button');
        for (var i = 0; i < btns.length; i++) {
            btns[i].addEventListener('click', function () {
                this.disabled = true;
                setTimeout(function () {
                    this.disabled = false;
                }.bind(this), 2000)
            })
        }
    </script>
</body>

5.4.4 call、apply、 bind总结

相同点:

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

不同点:

  • call和apply都会调用函数,并且改变函数内的this指向
  • call和apply传递的参数不一样,call传递的参数arg1,arg2...的形式,apply必须是数组形式[arg]
  • bind 不会调用函数,可以改变函数内部this指向

主要应用场景:

  • call经常做继承
  • apply经常和数组有关系。比如借助数学对象实现数组的最大值、最小值
  • bind不调用函数。但是还是改变this指向。比如上面例子的定时器改变内部this指向。

5.5严格模式

5.5.1 什么是严格模式

JavaScript除了提供正常模式外,还提供了一种严格模式(strict mode)。ES5的严格模式(IE10+ 的浏览器中才会被支持,旧版本浏览器会被省略)是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行JS代码。

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

  1. 消除了JavaScript语法的一些不合理、不严谨之处,减少了一些怪异行为。
  2. 消除代码运行的一些不安全之处,保障代码运行的安全
  3. 提高编译器效率,增加运行速度
  4. 禁用了一些在ECMAScript的未来版本中可能会定义的一下语法,为未来新版本的JavaScript做好铺垫。比如一些保留字class, enum, export, extends, import, super不能做变量名

5.5.2 开启严格模式

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


1. 为脚本开启严格模式:
为整个脚本开启严格模式,需要在所有语句之前放一个特定语句 "use strict" (或'use strict'

    <script>
        'use strict';//下面的js代码就会按照严格模式执行代码
        
    </script>
  • 因为'use strict'加了引号。所以老版本浏览器会把它当做字符串而忽略

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

    <script>
        (function () {
            'use strict';
        })()
    </script>


2. 为函数开启严格模式:

    <!-- 为某个函数开启严格模式 -->
    <script>
        function fn(){
            'use strict';
            // 下面的代码按照严格模式执行
        }
        function fun(){
            // 下面的代码按照普通模式执行
        }
    </script>

5.5.4 严格模式中的变化

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


1. 变量规定

  • 正常模式下,如果一个变量没有声明就赋值,默认是全局变量。
  • 严格模式禁止这样做,每个变量都必须先声明(var)再使用
  • 不能随意删除已经声明好的变量
<body>
    <script>
        'use strict';
        // 1. 我们的变量名必须先声明再使用
        // num = 10;
        // console.log(num);
        var num = 10;
        console.log(num);
        // 2.我们不能随意删除已经声明好的变量
        // delete num;
    </script>
</body>


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

  • 以前在全局作用域函数中的this指向window对象
  • 严格模式下全局作用域中函数中的this是undefined
  • 以前构造函数是不加new也可以调用,当普通函数来调用,this指向全局对象
  • 严格模式下,如果构造函数不加new调用,this会报错
  • new实例化的构造函数指向创建对象的实例
  • 定时器的this还是指向window
  • 事件、对象还是指向调用者
<body>
    <script>
        'use strict';
		//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);
        
    </script>
</body>


3.函数变化

  • 不能有重名的参数
  • 函数必须声明在顶层。新版本的JavaScript会引入“块级作用域”。为了与新版本接轨,不允许在非函数代码块内声明函数
<body>
    <script>
        'use strict';
        // 6. 严格模式下函数里面的参数不允许有重名
        function fn(a, a) {
           console.log(a + a);

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

5.6 高阶函数

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

  • 接收函数作为参数
<body>
    <div></div>
    <script>
        // 高阶函数- 函数可以作为参数传递(回调函数)
        function fn(a, b, callback) {
            console.log(a + b);
            callback && callback();//表示如果传入了callback那么就调用callback(),否则不调用
            //相当于
            //if(callback){
            //  callback()
            //}
        }
        fn(1, 2, function() {
            console.log('我是最后调用的');

        });

    </script>
</body>
  • 将函数作为返回值输出
<script>
    function fn(){
        return function() {}
    }
</script>
  • 此时fn就是一个高阶函数
  • 函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。

5.7 闭包

5.7.1 变量作用域

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

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

5.7.2 什么是闭包

闭包(closure)指有权访问另一个函数作用域中变量函数

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

被访问的局部变量所在的函数就是闭包函数

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

在Chrome中调试闭包:设置断点➡刷新进入断点调试➡查看Scope (里面会有两个参数(global 全局作用域、local 局部作用域))➡执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。

5.7.3 闭包的作用

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

5.7.4 闭包练习

  1. 点击li输出索引号(循环点击注册事件)
<body>
    <ul class="nav">
        <li>苹果</li>
        <li>香蕉</li>
        <li>橘子</li>
        <li></li>
    </ul>
    <script>
        // 闭包应用-点击li输出当前li的索引号
        // 1. 使用动态添加属性的方式
        var lis = document.querySelectorAll('li');
        // for(var i=0;i<lis.length;i++){
        //     lis[i].index=i;
        //     lis[i].addEventListener('click',function(){
        //         // console.log(i);//4
        //         console.log(this.index);
        //     })
        // }

        // 2.利用闭包的方式得到当前li的索引号(经典面试题)
        for (var i = 0; i < lis.length; i++) {
            // 利用for循环创建4个立即执行函数
            // 立即执行函数也称为 小闭包 因为立即函数里的任意一个函数都可以使用它的变量
            (function (i) {
                lis[i].onclick = function () {
                    console.log(i);
                }
            })(i);
        }
    </script>
</body>
  1. 3秒后,打印所有的li元素的内容(循环中的setTimeout())
<body>
    <ul class="nav">
        <li>苹果</li>
        <li>香蕉</li>
        <li>橘子</li>
        <li></li>
    </ul>
    <script>
        // 闭包应用-3秒后,打印所有的li元素的内容
        var lis = document.querySelector('.nav').querySelectorAll('li');
        for (var i = 0; i < lis.length; i++) {
            (function (i) {
                setTimeout(function () {
                    console.log(lis[i].innerHTML);
                }, 3000);
            })(i)
        }
    </script>
</body>

5.8 递归

5.8.1 什么是递归

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

就是说:函数内部自己调用自己,这个函数就是递归函数

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

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

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

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

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

5.8.2 浅拷贝和深拷贝

  • 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(地址)
  • 深拷贝拷贝多层,每一级别的数据都会拷贝
  • Object.assign(target,...source) es6新增方法可以浅拷贝
    • target:拷贝给谁
    • source:拷贝的对象
<body>
    <script>
        var obj = {
            name: 'mona',
            age: 18,
            sex: '女',
            fn: {
                mom: 'moly'
            },
            color: ['pink', 'blue']
        }
        //for in遍历我们的对象
        //for(变量 in 对象){ }
        var o = {};
        // for (var k in obj) {
        //     // console.log(k);//k 变量 输出得到的是属性名
        //     // console.log(obj[k]);// obj[k] 得到的是属性值
        //     console.log(k+','+obj[k]);//k 变量 输出得到的是属性名
        // }
        // 浅拷贝
        // Object.assign(o,obj)
        // console.log(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);
    </script>
</body>

6.正则表达式

6.1 正则表达式概述

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

  • 正则表达式的特点
  1. 灵活性、逻辑性和功能性非常的强
  2. 可以迅速地用极简单的方式达到字符串的复杂控制

6.2 正则表达式在JavaScript中的使用

6.2.1 创建正则表达式

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

  1. 通过调用RegExp对象的构造函数创建
var 变量名 = new RegExp(/表达式/);
  1. 利用字面量创建
var 变量名 = /表达式/;

6.2.2 测试正则表达式test

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

regexObj.test(str);
  • regexObj 是写好的正则表达式
  • str 我们要测试的文本
  • 就是检测str文本是否符合我们写的正则表达式规范
<body>
    <script>
        // 利用RegExp对象创建正则表达式
        var regexp=new RegExp(/123/);
        console.log(regexp);
        // 利用字面量创建
        var regexp1=/123/;//推荐使用
        console.log(regexp1);
        //test方法 检测字符串是否符合正则表达式要求的规范
        console.log(regexp1.test('123'));//true
        console.log(regexp1.test('abc'));//false
    </script>
</body>

6.3 正则表达式中的特殊字符

6.3.1 正则表达式的组成

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

6.3.2 边界符

  • 正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符说明
^表示匹配行首的文本(以谁开始)
$表示匹配行尾的文本(以谁结束)
var re = /abc/;//只要包含abc,这个字符串返回的都是true
 var reg = /^abc/;//前三个字符必须是abc才返回true
  • 如果^$在一起,表示必须是精确匹配。
var reg1 = /^abc$/;//精确匹配 要求必须是abc字符串才符合规范

<body>
    <script>
        // 边界符 ^ $
        var re = /abc/;//正则表达式不需要加引号,不管是数字型还是字符串型
        // /abc/ 只要包含abc这个字符串返回的都是true
        console.log(re.test('abc'));//true
        console.log(re.test('abcd'));//true
        console.log(re.test('aabcd'));//true
        console.log('----------------');

        var reg = /^abc/;
        console.log(reg.test('abc'));//true
        console.log(reg.test('abc'));//true
        console.log(reg.test('aabcd'));//false
        console.log('-----------------');
        var reg1 = /^abc$/;//精确匹配 要求必须是abc字符串才符合规范
        console.log(reg1.test('abc'));//true
        console.log(reg1.test('bc'));//false
        console.log(reg1.test('abcd'));//false
        console.log(reg1.test('aabcd'));//false
        console.log(reg1.test('abcabc'));//false
    </script>
</body>

6.3.3 字符类

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

  • []方括号内部
 var rg = /[abc]/;//只要包含a 或者包含 b 或者包含c 都返回true
  • /^[]$/ 方括号外部
var rg1 = /^[abc]$/;//三选一 只有a 或者是b 或者是c这三个字母才返回true
  • [-]方括号内部 范围符
var reg = /^[a-z]$/; //26个小写英文字母中的任何一个字母返回true 
// [a-z]表示的是a到z这个范围
  • [^] 方括号内部 取反符 只要包含方括号内的字符,都返回 false
 // 如果中括号里面有^ 表示取反的意思 千万和 我们边界符 ^ 别混淆
 var reg2 = /^[^a-zA-Z0-9_-]$/;
  • [-] 字符组合
 var reg1 = /^[a-zA-Z0-9_-]$/;
// 多选一 26个英文字母(大写和小写字母、0-9的数字、下划线、短横线)中的任何一个都返回true 
<body>
    <script>
        var rg = /[abc]/;//只要包含a 或者包含 b 或者包含c 都返回true
        console.log(rg.test('andy'));//true
        console.log(rg.test('baby'));//true
        console.log(rg.test('color'));//true
        console.log(rg.test('red'));//false
        console.log('----------');

        var rg1 = /^[abc]$/;//三选一 只有a 或者是b 或者是c这三个字母才返回true
        console.log(rg1.test('aa'));//false
        console.log(rg1.test('a'));//true
        console.log(rg1.test('b'));//true
        console.log(rg1.test('c'));//true
        console.log(rg1.test('abc'));//false
        console.log('----------');

        var reg = /^[a-z]$/; //26个英文字母中的任何一个字母返回true 
        // [a-z]表示的是a到z这个范围
        console.log(reg.test('a'));//true
        console.log(reg.test('z'));//true
        console.log(reg.test(1));//false
        console.log(reg.test('A'));//false
        console.log('----------');

        // 字符组合
        var reg1 = /^[a-zA-Z0-9_-]$/;
        // 多选一 26个英文字母(大写和小写字母、0-9的数字、下划线、短横线)中的任何一个都返回true 
        console.log(reg1.test('a'));//true
        console.log(reg1.test('B'));//true
        console.log(reg1.test(8));//true
        console.log(reg1.test('-'));//true
        console.log(reg1.test('_'));//true
        console.log(reg1.test('!'));//false
        console.log('----------------');

        // 如果中括号里面有^ 表示取反的意思 千万和 我们边界符 ^ 别混淆
        var reg2 = /^[^a-zA-Z0-9_-]$/;
        console.log(reg2.test('a'));//false
        console.log(reg2.test('B'));//false
        console.log(reg2.test(8));//false
        console.log(reg2.test('-'));//false
        console.log(reg2.test('_'));//false
        console.log(reg2.test('!'));//true
    </script>
</body>

6.3.4 量词符

用来设定某个模式出现的次数

量词符说明
*重复0次或者更多次
+重复一次或者更多次
重复0次或者1次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n次到m次
  • *相当于>=0 可以出现0次或多次 [0,+∞)
<body>
    <script>
        var reg = /^a*$/;
        console.log(reg.test(''));//true
        console.log(reg.test('a'));//true
        console.log(reg.test('aaa'));//true
    </script>
</body>
  • +相当于出现>=1次或者很多次 [1,+∞)
<body>
    <script>
        var reg = /^a+$/;
        console.log(reg.test(''));//false
        console.log(reg.test('a'));//true
        console.log(reg.test('aaa'));//true
    </script>
</body>
  • ?相当于 1||0 就是说不出现或出现一次才是true
<body>
    <script>
        var reg = /^a?$/; 
        console.log(reg.test(''));//true
        console.log(reg.test('a'));//true
        console.log(reg.test('aaa'));//false
    </script>
</body>
  • {3} 就是重复3次
<body>
    <script>
        var reg = /^a{3}$/;
        console.log(reg.test(''));//false
        console.log(reg.test('a'));//false
        console.log(reg.test('aaa'));//true
    </script>
</body>
  • {3, } 重复 >=3次 [3,+∞)
<body>
    <script>
        // {3,} 就是重复三次及以上
        var reg = /^a{3,}$/;
        console.log(reg.test(''));//false
        console.log(reg.test('a'));//false
        console.log(reg.test('aaa'));//true
        console.log(reg.test('aaaa'));//true
    </script>
</body
  • {3,16} 重复 >=3且<=6次 [3,6]
<body>
    <script>
        var reg = /^a{3,6}$/;
        console.log(reg.test(''));//false
        console.log(reg.test('a'));//false
        console.log(reg.test('aaa'));//true
        console.log(reg.test('aaaa'));//true
        console.log(reg.test('aaaaaa'));//true
        console.log(reg.test('aaaaaaa'));//false
    </script>
</body>

6.3.5 简单表单验证案例(用户名验证)

功能需求:

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

分析:

  1. 用户名只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为 6~16位.
  2. 首先准备好这种正则表达式模式 /^[a-zA-Z0-9-_]{6,16}$/
  3. 当表单失去焦点就开始验证.
  4. 如果符合正则规范, 则让后面的span标签添加 right 类.
  5. 如果不符合正则规范, 则让后面的span标签添加 wrong 类.
    <style>
        span {
            color: #aaa;
            font-size: 14px;
        }

        .right {
            color: green;
        }

        .wrong {
            color: red;
        }
    </style>
<body>
    <input type="text" class="uname"><span>请输入用户名</span>
    <script>
        // 量词是设定某个模式出现的次数
        // var reg=/^[a-zA-Z0-9_-]$/;//有[] 限定了只能多选一
        var reg = /^[a-zA-Z0-9_-]{6,16}$/;//有[] 限定了只能多选一 加上量词符限定出现的次数
        // {6,16} 两次中间不能有空格

        var uname = document.querySelector('.uname');
        var span = document.querySelector('span')
        uname.onblur = function () {
            if (reg.test(this.value)) {
                span.className = 'right';
                span.innerHTML = '用户名输入正确'
            } else {
                span.className = 'wrong';
                span.innerHTML = '用户名输入错误'
            }
        }
    </script>
</body>

6.3.6 括号总结

<body>
    <script>
        // 中括号 字符集合。匹配方括号中的任意字符
        var reg=/^[abc]$/;//a||b||c都可以
        // 大括号 量词符、里面表示重复的次数
        var reg1= /^abc{3}$/; //让c重复三次 abccc才会返回true
        // 小括号 优先级
        var reg2= /^(abc){3}$/;//让abc重复三次 才会返回true
    </script>
</body>

6.3.7 预定义类

指的是某些常见模式的简写方式

预定义类说明
\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]

6.3.8 验证座机号码

全国座机号码 两种格式:010-12345678或者0530-1234567

正则里面的或者符号 |

<body>
    <script>
        // 全国座机号有两种格式:010-12345678  0530-1234567
        // 正则里面的 或者 符号 |
        // var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/;
        var reg = /^\d{3,4}-\d{7,8}$/;
        console.log(reg.test('010-12345678'));
        console.log(reg.test('0530-1234567'));
    </script>
</body>

6.3.9 特殊需求表达式

查询地址:c.runoob.com/front-end/8…

6.4 正则表达式中的替换

6.4.1 replace替换

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

stringObject.replace(regexp/substr,replacement)
  • regexp/substr:被替换的字符串 或者 正则表达式
  • replacement:被替换为的字符串
  • 返回值是一个替换完毕的新字符串
// 替换 replace
var str = 'andy和red';
var newStr = str.replace('andy','baby');
var newStr = str.replace(/andy/,'baby');

6.4.2 正则表达式参数

/表达式/[switch]

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

  • g:全局匹配
  • i:忽略大小写
  • g+i:全局匹配+忽略大小写

正则替换(过滤、替换)

<body>
    <textarea name="" id="message"></textarea><button>提交</button>
    <div></div>
    <script>
        var text = document.querySelector('textarea');
        var btn = document.querySelector('button');
        var div = document.querySelector('div');

        btn.addEventListener('click', function () {
            div.innerHTML = text.value.replace(/激情|gay/g, '**')
        })
    </script>
</body>