javaScript高级教程(pink老师版)

441 阅读21分钟

js高级代码素材

一、JavaScript面向对象

1. 面向对象编程介绍

1.1 两大编程思想

  • 面向对象
  • 面向过程

1.2 面向过程编程

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

1.3 面向对象编程

面向对象 编程,即 OOP(Object Oriented Programming) 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。

面向对象的特性:

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

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

1.4.1 面向过程

  • 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
  • 缺点:没有面向对象易维护、易复用、易扩展。

1.4.2 面向对象

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
  • 缺点:性能比面向过程低。

2. ES6中的类和对象

2.1 对象

现实生活中:万物皆对象,对象是 一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。

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

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

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

2.2 类 class

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

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

面向对象的思维特点:

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

2.3 创建类和对象

语法:

    class ClassName {
        // class body
    }

创建实例:

    let obj = new ClassName();

类必须使用 new 实例化对象

2.4 类 constructor 构造函数

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

// 创建一个学生类
class Student {
    constructor(uname, age, major) {
        this.uname = uname;
        this.age = age;
        this.major = major;
    }
}
// 类的实例化--创建对象
let peter = new Student("Peter", 21, "CS");
console.log(peter.uname); // Peter

注意:

  • 通过 class 关键字创建类, 类名我们还是习惯性定义首字母大写
  • 类里面有个 constructor 函数,可以接受传递过来的参数,同时返回实例对象
  • constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
  • 生成实例 new 不能省略
  • 最后注意语法规范, 创建类:类名后面不要加小括号。生成实例:类名后面加小括号, 构造函数不需要加 function

2.5 类中添加方法

class Student {
    // 类的共有属性放到 constructor 中
    constructor(uname, age, major) {
        this.uname = uname;
        this.age = age;
        this.major = major;
    }
    // 类中添加方法
    sing() {
        console.log(this.uname + "会唱歌");
    }
}
// 创建实例
let peter = new Student("Peter", 18, "化学");
peter.sing(); // Peter会唱歌

(1)类里面所有的函数不需要写 function
(2)多个函数方法之间不需要添加逗号

3. 类继承extends和super关键字

3.1 类的继承

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

class Son extends Father {
    // class body
}

在继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的,如果没有,就去查找父类有没有这个方法,有就执行父类的(就近原则

3.2 super关键字

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

class Person {
    constructor (uname, age) {
        this.uname =uname;
        this.age = age;
    }
    shuchu(){
        return '我的名字是'+this.uname+',年龄'+this.age+'岁'
    }
}
class Student extends Person {
    constructor (uname, age, major) {
        // super 将子类的参数传递给父类构造函数,减少代码量
        super(uname, age);
        // 子类可以有自己独有的属性
        this.major = major;
    }
    shuchu(){
        console.log(super.shuchu())
    }
}
let rick = new Student("Rick", 22, "数学");
rick.shuchu()
//因为shuchu这个函数的参数都是父亲Person的实例的属性,他们的this都指向父亲的实例,无法使用子类的参数
//所以我们要给父亲传参,调用super类

3.3 super必须放在子类this之前

class Father {
    constructor(x, y){
        this.x = x
        this.y = y        
    }
    sum(){
        console.log(this.x + this.y)
    }
}
// 子类继承父类加法方法 同时 扩展减法方法
class Son extents 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(8, 2)
son.subtract()  // 6
son.sum()   // 10

4. 使用类的注意点

  • 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
  • 类里面的共有属性和方法一定要加 this 使用
  • 类里面的this指向问题:constructor 里面的this指向实例对象,方法里面的this指向这个方法的调用者

案例分析如下:

var that;
var _that;
class Star {
    constructor (uname, age) {
        that = this;
        this.uname = uname;
        this.age = age;
        // btn按钮调用sing方法
        this.btn = document.querySelector("button");
        this.btn.onclick = this.sing;
        // constructor 里面的this 指向的是 创建的实例对象
        console.log("constructor: ", this);
    }
    sing() {
        // 这个sing方法里面的 this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
        console.log("sing:", this); // button
        console.log(that.uname); // that里面存储的是constructor里面的this
    }
    dance() {
        // 这个dance里面的this 指向的是实例对象rick,因为rick调用了这个函数
        _that = this
        console.log("dance:", this);
    }
}
let rick = new Star("Rick", 20);
rick.dance();
console.log(that === rick)  // true
console.log(_that === rick)  // true

案例一:面线对象tab栏

二、构造函数和原型

1. 构造函数和原型

1.1 概述

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

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

创建对象可以通过以下三种方式:
1.对象字面量
2.new Object()
3.自定义构造函数

<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('I can sing')
        }
    }
    var ldh = new Star('刘德华',40)
    console.log(ldh)
    ldh.sing()  // I can sing
</script>

1.2 构造函数

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

在JS中,使用构造函数时要注意以下两点:
①构造函数用于创建某一类对象,其首字母要大写
②构造函数要和new一起使用才有意义

new在执行时会做四件事情:
① 在内存中创建一个新的空对象
② 让this指向这个新对象
③ 执行构造函数里面的代码,给这个新对象添加属性和方法
④ 返回这个新对象(所以构造函数里面不需要return)

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

  • 静态成员:在构造函数本身上添加的成员称为静态成员只能由构造函数本身来访问
  • 实例成员:在构造函数内部创建的对象成员称为实例成员只能由实例化的对象来访问

截屏2023-01-30 下午9.54.45.png

1.3 构造函数的问题

构造函数方法很好用,但是存在浪费内存的问题 截屏2023-01-30 下午9.57.57.png

//比较的是内存地址,两个实例对象开辟的内存地址不同,所以返回false
console.log( ldh.sing === zxy.sing )  // false

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

1.4 构造函数原型 prototype

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

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

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

截屏2023-01-30 下午10.10.06.png 1.原型是什么?
一个对象,我们也称prototype为原型对象

2.原型的作用是什么?
共享方法

1.5 对象原型 __proto__

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

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

截屏2023-01-30 下午10.37.44.png

截屏2023-01-30 下午10.35.15.png

1.6 constructor 构造函数

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

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

<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);

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

    console.log(Star.prototype.constructor);
    console.log(ldh.__proto__.constructor);
</script>
截屏2023-01-31 下午10.43.14.png
<script>
    function Star(uname, age){
        this.uname =uname; 
        this.age =age;
    }
    // 很多情况下,需要手动的利用constructor这个属性指回 原来的构造函数
    Star.prototype = {
        constructor: Star,// 不加这句话结果是图一,加上这句话结果是图二
        sing: 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>

图一:
截屏2023-01-31 下午10.51.44.png
图二:
截屏2023-01-31 下午10.53.37.png

如果修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数。

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

截屏2023-01-31 下午11.28.31.png

1.8 原型链

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

image.png image.png

  • 构造函数有原型对象 prototype 。
  • 构造函数原型对象 prototype 里面有 constructor 指向构造函数本身。
  • 构造函数可以通过原型对象添加方法。
  • 构造函数创建的实例对象有__proto__原型指向构造函数的原型对象。

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

① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__ 指向的 prototype原型对象)。
③ 如果还没有就查找原型对象的原型(Object的原型对象)。
④ 以此类推一直找到Object为止(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

1.10 原型对象this指向

1.11 扩展内置对象

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

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

2. 继承

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

2.1 call()

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

    // call 方法
    function fn(){
        console.log('drinking')
        console.log(this)  // 指向window
    }
    var obj = {
        name: 'tony'
    }
    // 1. call() 可以调用函数
    fn.call()
    // 2. call()改变函数的this指向,此时这个函数的this就指向了obj这个对象
    fn.call(obj)

2.2 借助构造函数继承父类属性

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

    function Father(uname, age){
    	// 这里的this指向父类构造函数的对象实例
        this.uname = uname
        this.age = age
    } 
    Father.prototype.money = function(){
        console.log('money')
    } 
    // 2.3 借助原型对象继承父类方法
    Son.prototype = new Father()
    // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
    Son.prototype.constructor = Son
    Son.prototype.exam = function(){
        console.log('exam')
    }
    function Son(uname, age, sex){
    	// 这里的this指向子类构造函数的对象实例
        Father.call(this, uname, age)   
        this.sex = sex
    }
    var s = new Son('xiaoxiao', 16, '男')
    console.log(s.uname) // xiaoxiao
    console.log(s)

2.3 借助原型对象继承父类方法

正确方法在2.2代码中

// 下面这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = Father.prototype
// 这是子构造函数单独所有的方法
Son.prototype.exam = function(){
    console.log('exam')
}
console.log(Father.prototype) // Father也有exam方法

3. 类的本质

1. class的本质还是function

  • 类有原型对象 prototype
  • 类对象 prototype 里面有 constructor 指向类本身。
  • 类可以通过原型对象添加方法。
  • 类创建的实例对象有__proto__原型指向类的prototype原型对象。

2. ES6的类绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

ES6的类其实就是语法糖。(语法糖就是一种便捷写法,简单理解,有两种方法实现同样的功能,但其中一种更清晰、方便,这个方法就是语法糖。)

三、ES5新增方法概述

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

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

1.数组方法

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

(1)array.forEach()

array.forEach(function(currentValue, index, arr))

  • currentValue: 数组当前项的值
  • index: 数组当前项的索引
  • arr: 数组对象本身

(2)array.filter()

array.filter(function(currentValue, index, arr))

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

(3)array.some()

array.some(function(currentValue, index, arr))

  • some()方法用于检测数组中的元素是否满足指定条件,通俗点查找数组中是否有满足条件的元素
  • 注意:返回值是布尔值,如果找到这个元素返回true,如果查不到就返回false
  • 如果找到第一个满足条件的元素,则中止循环,不再继续查找
  • currentValue: 数组当前项的值
  • index: 数组当前项的索引
  • arr: 数组对象本身
    var arr = [12, 88, 6, 28]
    var flag = arr.some(function(item, index){
        return item>100
    })
    console.log(flag)

总结:

(1) filter 查找满足条件的元素,返回的是一个数组,而且是把所有满足条件的元素返回回来
(2) some 查找满足条件的元素是否存在,返回的是一个布尔值,如果查找到第一个满足条件的元素就终止循环
(3) 在forEach里面return不会中止迭代,在some里面遇到return true就是中止遍历,迭代效率更高。

2.字符串方法

(1) trim()

str.trim()

方法会从一个字符串的两端删除空白字符串。trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。

(2) Object.defineProperty()

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

Object.defineProperty(obj, prop, descriptor)

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

image.png

    var obj = {
        id: '1',
        name: '小米',
        price: '1999'
    }
    // 1.以前的对象添加和修改属性的方式
    // obj.num = 10
    // 2.用Object.defineProperty()
    Object.defineProperty(obj, 'num', {
        value: 10
    })
    Object.defineProperty(obj, 'id', {
        writable: false,  // false不允许修改这个属性值
        configurable:false,  // false不允许被删除
    })
    obj.id = 2 // 无效
    console.log(obj)
    console.log(Object.keys(obj)) //  ['id', 'name', 'price']
    delete obj.id  // 无效 
    console.log(obj)

(3) Object.keys()

用于获取对象自身所有的属性。

Object.keys(obj)

  • 效果类似for…in
  • 返回一个由属性名组成的数组

四、函数

1. 函数的定义和调用

1.1 函数的定义方式

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

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

(3)var fn = new Function('参数1', '参数2', '函数体')

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

截屏2023-02-11 下午9.52.58.png

1.2 函数的调用方式

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

2. this

2.1 函数内 this 的指向

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

        // 函数的不同调用方式决定了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);
        })();

截屏2023-02-11 下午10.01.28.png

常用来改变this指向的三种方法为bind()、call()、apply()。

(1)call方法

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

fun.call(thisArg, arg1, arg2)

call 的主要作用可以实现继承。

(2)apply方法

截屏2023-02-11 下午10.35.43.png

(3)bind方法

截屏2023-02-11 下午10.41.32.png

返回的是原函数改变this之后产生的新函数

如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用bind。
截屏2023-02-11 下午11.00.03.png

2.2 call apply bind总结

相同点:都改变函数内部的this指向。

区别点

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

主要应用场景:

  1. call经常做继承。
  2. apply经常跟数组有关系,比如借助于数学对象实现数组最大值最小值。
  3. bind不调用函数,但是还想改变this指向,比如改变定时器内部的this指向。

3. 严格模式

3.1 什么是严格模式

image.png

3.2 开启严格模式

image.png image.png

image.png

3.3 严格模式中的变化

(1)变量规定

① 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用。
② 严禁删除已经声明变量。例如,delete x; 语法是错误的

(2)严格模式下this指向问题

① 以前在全局作用域函数中的this指向window对象。
② 严格模式下全局作用域中函数中的this是undefined。
③ 以前构造函数不加new也可以调用,当普通函数,this指向全局对象。
④ 严格模式下,如果构造函数不加 new 调用,this会报错。
⑤ new 实例化的构造函数指向创建的对象实例。
⑥ 定时器 this 还是指向 window。
⑦ 事件、对象还是指向调用者。

(3)函数变化

① 函数不能有重名的参数
② 函数必须声明在顶层,新版本的JS会引入"块级作用域"(ES6已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。

4. 高阶函数

image.png

5. 闭包

5.1变量作用域

变量根据作用域的不同分为两种:全局变量和局部变量。
1.函数内部可以使用全局变量。
2.函数外部不可以使用局部变量。
3.当函数执行完毕,本作用域内的局部变量会销毁。

5.2什么是闭包

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

5.3闭包的作用

闭包的主要作用:延伸变量的作用范围

5.4闭包应用

(1)点击li输出当前li的索引号

(2)定时器中的闭包

(3)计算打车价格

思考:

截屏2023-02-16 下午10.28.48.png 截屏2023-02-16 下午10.33.49.png

5.5闭包总结

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

(2)闭包的作用是什么?延伸变量的作用域。

6. 递归

6.1什么是递归

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。(简单理解:函数内部自己可以调用自己,这个函数就是递归函数)

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

由于递归很容易发生"栈溢出"错误,所以必须要加退出条件return。

利用递归求斐波那契数列: 截屏2023-02-20 下午9.27.33.png

6.2 浅拷贝和深拷贝

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

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

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

    var obj = {
        id: 1,
        name: 'andy',
        msg:{
            age: 18
        },
        color:['pink', 'green']
    }
    var o = {}
    for(var k in obj){  // k是属性名, obj[k]是属性值
        o[k] = obj[k]
    }
    
    var t = {}
    Object.assign(t, obj) //浅拷贝
    console.log(t)
    
    var deep = {}
    // 封装函数(深拷贝)
    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], oldObj[k])
              }else{
                  // 4.属于简单数据类型
                  newObj[k] = item
              }
              
        }
    }
    deepCopy(deep, obj)
    console.log(deep)
    deep.msg.age = 20
    console.log(obj)

四、正则表达式

1.正则表达式概述

1.1什么是正则表达式

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

截屏2023-02-20 下午10.28.57.png

1.2正则表达式的特点

截屏2023-02-20 下午10.31.27.png

2.正则表达式在Javascript中的使用

2.1创建正则表达式

在js中,有两种方式来创建正则表达式。

    // 1.利用RegExp对象来创建正则表达式
    var regexp = new RegExp(/123/)
    // 2.利用字面量创建
    var rg = /123/  
    // 正则表达式里面不需要加引号,不管是数字型还是字符串型

2.2测试正则表达式 test

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

    var rg = /123/    // 只要包含有123这个字符串返回的都是true
    console.log(rg.test(123)) // true 判断正则表达式中是否出现了 123
    console.log(rg.test('abc')) // false
    console.log(rg.test(1))     // false
    console.log(rg.test(4123))  // true

3.正则表达式中的特殊字符

3.1正则表达式的组成

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

3.2边界符

正则表达式中的边界符(位置符)用来提示字符所在的位置,主要有两个字符。 截屏2023-02-20 下午10.55.56.png
如果 ^ 和 $ 在一起,表示必须是精确匹配。

    // 以123开头
    var rg2 = /^123/
    console.log(rg2.test(1234)) // true
    console.log(rg2.test(4123)) // false
    
    // 以123开头并且以123结尾(必须是123字符串才符合规范)
    var rg2 = /^123$/
    console.log(rg2.test(123)) // true
    console.log(rg2.test(123123)) // false

3.3字符类

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

    // 只要包含有a 或者包含有b 或者c 都返回true
    var rg = /[abc]/
    console.log(rg.test('andy'))  
    
    // 三选一,只有是a 或是b 或是c 这三个字母才返回true
    var rg2 = /^[abc]$/
    console.log(rg2.test('aa')) // false
    console.log(rg2.test('b'))  // true
    
    // 26个英文字母任何一个字母返回true
    var rg3 = /^[a-z]$/
    
    // 26个英文字母大写和小写任何一个字母返回true
    var rg4 = /^[a-zA-Z]$/
    
    var rg5 = /^[a-zA-Z0-9]$/
    var rg6 = /^[a-zA-Z_-]$/
    
    // 如果中括号里面有 ^ 表示取反的意思
    var rg7 = /^[^a-zA-Z]$/

3.4量词符

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

    var rg = /^a*$/  
    console.log(rg.test(''))  // true
    console.log(rg.test('a'))  // true
    console.log(rg.test('ab'))  // false
    console.log(rg.test('aaaa'))  // true

用户名验证:/^[a-zA-Z0-9]{6,16}$/ {6,16}中间不要有空格

3.5括号总结

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

(2)中括号,字符集合,匹配方括号中的任意字符。

(3)小括号,表示优先级。

    // 这是让 abc 重复三次
    var rg = /^(abc){3}$/

3.6预定义类

预定义类值得是某些常见模式的简写方式。 image.png

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

    // 正则里面的或者符号 |
    var rg1 = /^\d{3}-\d{8}|\d{4}-\d{7}$/

4.正则表达式中的替换

4.1 replace替换

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

image.png

    var str = 'andy和red'
    //var newStr = str.replace('andy','baby')
    
    var newStr2 = str.replace(/andy/,'baby')

4.2 正则表达式参数

image.png