前端与移动开发----JS高级----面向对象编程,类与实例对象,继承,严格模式,模板字符串,class封装tab栏

JS高级01

回顾上阶段

Javascript组成

  • ECMAScript: 基础语法 (变量, 表达式, 循环, 判断, 函数, 对象等)
  • DOM: document 操作标签(获取, 增加, 插入, 删除, 移动), 事件(鼠标, 键盘, 表单), 标签属性等, 和标签打交道
  • BOM: window对象, 定时器, 计时器, (location, navigator, history)等, 和浏览器打交道

与现阶段区别

之前学的JS基础语法, 这个阶段学习一些高级用法和思想

现在学习:

  • 面向过程开发 / 面向对象开发 区别
  • 面向对象开发
    • 类 - 实例对象
    • 继承
  • 构造函数 - 了解JS对象底层运作的机制
    • 显示原型 prototype
    • 隐式原型 __proto__
    • 原型链
  • 各种函数
    • 闭包
    • 高阶函数
    • 递归函数
  • ES6新增语法使用 (现在学的是ES5版本, ES6只是新增了一些关键字和语法)

1. 面向对象编程

面向过程开发

​ 分析项目所需要的步骤

​ 用基础代码, 把这些步骤一步一步实现, 依次执行

面向对象开发

​ 分析项目所需要的对象

​ 用对象调用他们身上的方法

面向过程和对象区别

例1: 把大象装进冰箱

  • 面向过程思想:

在这里插入图片描述

  • 面向对象思想:
    • 大象对象
      • 走路方法
    • 冰箱对象
      • 开门方法
      • 关门方法

例2: 造飞机

一个人叠纸飞机, 一步一步来就ok - (面向过程思想编码)

造真飞机, 功能复杂庞大, 代码量多, 需要多个对象参与 - (面向对象思想编码)

* A对象 -> 负责造外壳
* B对象 -> 造发动机
* C对象 -> 造机仓
* D对象 -> 造轮子
* ...

总结

面向过程:小项目

面向对象:多人合作大项目

面向对象使用场景

把某个功能封装成插件, 我们可以把相关代码放到类里, 然后实例化对象出来, 调用, 例如前面学过的Swiper等插件

面向对象就是一种思想, 代码还是JavaScript

类与实例对象

在这里插入图片描述

  • 什么是类?

    类是一个模板, 里面有属性和方法

  • 类能干什么?

    定义一套模板, 批量产生实例对象

  • 什么是实例对象?

    new 出来的对象

1.0 类模板 - 属性

记住语法格式: 创建类和实例对象

// ES6新增的class关键字定义类
/*
	class 类名 {
        constructor (形参1, 形参2) { 
            this.属性名 = 形参1
            this.属性名 = 形参2
        }
	}
*/
// 定义类
class Star{
    
    
    constructor (theName, theAge) {
    
      // 添加属性
        this.name = theName; 
        this.age = theAge;
    }
}
// 实例化对象
var per1 = new Star("刘德华", 18);
console.log(per1);
var per2 = new Star("张学友", 20);
console.log(per2);

1.1 new的作用

  • 创建一个空白对象{}
  • 执行并替代constructor里this的值
  • 在this(空白对象{})身上添加属性和对应的值
  • constructor没有return, 就默认返回这个对象到调用处
class Star{
    
    
    constructor (theName, theAge) {
    
     
        this.name = theName; 
        this.age = theAge;
    }
}
// 实例化对象
var per1 = new Star("刘德华", 18);
console.log(per1);
var per2 = new Star("张学友", 20);
console.log(per2);
// 总结:
// 1. 类是模板, 定义属性和方法的地方
// 2. new的作用:
//    (1): 创建一个空白对象{}
//    (2): 替代constructor里默认this的值
//    (3): 执行constructor函数, 在this身上添加属性和值
//    (4): 自动返回这个对象{name:值, age:值}

执行顺序和对应关系如图

在这里插入图片描述

1.2 类模板 - 方法

类里除了属性以外, 还可以定义方法, 让所有的实例对象都有这个模板里的方法使用

class Star {
    
    
    constructor(theName, theAge) {
    
    
        this.name = theName;
        this.age = theAge;
    }
    // 方法写在这里
    sing (songName) {
    
    
        console.log(`${
      
      this.name}会唱${
      
      songName}`);
    }
}

// 实例化对象
var per1 = new Star("刘德华", 18);
console.log(per1);
var per2 = new Star("张学友", 20);
console.log(per2);

// 使用属性
console.log(per1.name);
// 使用方法
per1.sing("冰雨");
// 总结:
// 1. 类是模板 - 定义属性和方法
// 2. new 类名() - 创建实例对象
// 3. 对象.属性 - 原地返回属性的值
// 4. 对象.方法() - 调用方法执行(跟普通函数调用一模一样)

1.3 this的指向

this是函数内的隐藏变量, 无需声明直接使用

this变量的值, 在代码执行时, 才能知道里面的值 (运行绑定)

class Star {
    
    
    constructor(theName, theAge) {
    
    
        this.name = theName;
        this.age = theAge;
    }
    sing(songName) {
    
    
        console.log(`${
      
      this.name}会唱${
      
      songName}`);
    }
}

var per1 = new Star("刘德华", 18);
per1.sing("冰雨");

var per2 = new Star("张学友", 20);
per2.sing("饿狼传说");

// 总结:
// new 触发constructor , 函数里 this指向实例对象 (特殊记忆)
// 普通函数里, this指向调用者 (口诀通用)

图示如下 - 包括执行流程

在这里插入图片描述

1.4 练习

// 练习1: 定义人类Person, 属性有name, sex, 方法有run(方法里打印一句"人类会跑")

class Person {
    
    
    constructor (tName, tSex) {
    
     // 形参运行时, 用的传入的具体的值
        this.name = tName; // 把具体的值, 保存在this对象的属性name上 {name: 值}
        this.sex = tSex
    }
    run () {
    
    
        console.log("人类会跑");
    }
}

// 练习2: 用Person类, 实例化2个对象, 并且传入名字和性别的值, 打印2个实例对象 (本题: 模板, 实例对象之间的关系)

class Person {
    
    
    constructor (tName, tSex) {
    
     // 形参运行时, 用的传入的具体的值
        this.name = tName; // 把具体的值, 保存在this对象的属性name上 {name: 值}
        this.sex = tSex
    }
}
var per1 = new Person("小黄", "男");
var per2 = new Person("小花", "女");

console.log(per1);
console.log(per2);

// 练习3: 定义学生类Student, 属性有name, sex, hobby, 方法有study(方法里代码: console.log(this.name + “在学习”))

class Student {
    
    
    constructor (name, sex, hobby) {
    
    
        this.name = name;
        this.sex = sex;
        this.hobby = hobby;
    }
    study () {
    
    
        console.log(this.name + "在学习");
    }
}

// 练习4: 用Student类, 实例化1个对象, 传入名字,性别,爱好, 实例对象调用study方法, 观察打印结果 (本题: 思考this.name, 里的this到底是谁?)

class Student {
    
    
    constructor (name, sex, hobby) {
    
    
        this.name = name;
        this.sex = sex;
        this.hobby = hobby;
    }
    study () {
    
    
        console.log(this.name + "在学习");
    }
}
var stu = new Student("小明", "男", "打游戏");
stu.study();

// 练习5: 定义工具类Tool, 无属性, 实现2个方法, 一个求3个数的和, 一个求3个数最大值, 方法里都返回最后的结果, 实例化对象, 调用方法, 传参后, 接收返回的结果打印

class Tool {
    
    
    getSum (num1, num2, num3) {
    
    
        return num1 + num2 + num3;
    }
    getMax (numA, numB, numC) {
    
    
        return Math.max(numA, numB, numC); // Math.max() 会返回最大值在原地, 但是外面调用getMax()方法的地方还要这个最大值, 所以需要return出去
    }
}
var toolObj = new Tool();
var result1 = toolObj.getSum(20, 30, 40);
console.log(result1);

console.log(toolObj.getMax(10, 15, 18));

总结: 多个方法, 使用相同的属性值(this.xxx), 则constructor里this.xxx接收, 如果只有函数自己用, 就用形参传递 (无论写哪里都是对的)

2. class更多用法

2.0 继承 - extends

什么是继承

子类的实例对象, 继承父类里的属性和方法

// 1. 父类 - 求和, 求乘积方法
class Father {
    
    
    constructor(theX, theY) {
    
    
        this.x = theX;
        this.y = theY;
    }
    getSum() {
    
    
        console.log(this.x + this.y);
    }
    getProduct() {
    
    
        console.log(this.x * this.y);
    }
}

// 2. 子类也想继承使用
// 继承语法: class 子类 extends 父类 {} 
class Son extends Father {
    
     }

// 3. 实例化子类对象, 调用方法
var son = new Son(10, 29);
son.getSum();
son.getProduct();
console.log(son.x, son.y);

// 总结: 子类的实例对象, 能够继承并使用父类的属性和方法(多亏了extends关键字)

2.1 继承 - super 调用 父类constructor

需求: 子类要实现取随机数方法, 但是也想有求和, 求乘积的方法, 所以继承

class Father {
    
    
    constructor(theX, theY) {
    
    
        this.x = theX;
        this.y = theY;
    }
    getSum() {
    
    
        console.log(this.x + this.y);
    }
    getProduct() {
    
    
        console.log(this.x * this.y);
    }
}

class Son extends Father {
    
     
    // 子类自己也有属性
    constructor(tx, ty, randNum){
    
    
        super(tx, ty); // 要执行父类的constructor, 把x和y属性和值绑定上
        this.randN = randNum;
    }
    // 子类自己的方法
    getRandom() {
    
    
        console.log(Math.floor(Math.random() * this.randN));
    }
}
var son = new Son(10, 29, 100);
son.getSum();
son.getProduct();
son.getRandom();
console.log(son.x, son.y, son.randN);

// 总结:
// 1. super的作用
//   (1): 触发父类(extends后面)的类的constructor函数执行
//   (2): 修改父类constructor里的this为当前new的实例对象
//   (3): super()执行完父类的constructor后返回到这里, 再执行完子类constructor后返回实例对象到new处
// 2. 如果子类constructor使用this, super必须在constructor的第一行

代码的执行顺序和this的值 (根据序号看)

在这里插入图片描述

2.2 继承 - super 调用 父类普通方法(了解)

需求: 子类求随机数的地方, 取值范围要参数1和参数2的乘积作为取值范围

class Father {
    
    
    constructor(theX, theY) {
    
    
        this.x = theX;
        this.y = theY;
    }
    getSum() {
    
    
        console.log(this.x + this.y);
    }
    getProduct() {
    
    
        console.log(this.x * this.y);
        return this.x * this.y;
    }
}

class Son extends Father {
    
     
    constructor(tx, ty, randNum){
    
    
        super(tx, ty); 
        this.randN = randNum;
    }
    getRandom() {
    
    
        // 在这里通过 super.父类普通函数名() 即可跳转到父类相应方法执行后, 返回结果到这里
        console.log(Math.floor(Math.random() * this.getProduct()));
    }
}
var son = new Son(10, 29, 100);
son.getRandom();

// 总结:
// this指向当前函数的调用者
// new触发constructor执行, 改变了this指向, 指向为实例对象

代码执行过程

在这里插入图片描述

2.3 练习

/*Person类里属性: name和sex
Person类里方法: run - 打印一个字符串即可
 			    say - 打印当前调用者名字			    
Student类继承自Person类
Student里属性: hobby
Student里方法: play - 返回拼接的字符串: 调用者名字和爱好
实例化子类对象, 传入3个实际参数的值
提示, 子类里的super()应该把收到的实参值传递给父constructor函数, 把值绑定到实例对象的name和sex属性上
最后调用run和say观察打印结果
调用play方法拿到返回值, 然后打印
*/
class Person {
    
    
    constructor(name, sex) {
    
    
        this.name = name;
        this.sex = sex;
    }
    run() {
    
    
        console.log('abc');
    }
    say() {
    
    
        return this.name;
    }
}

class Student extends Person {
    
    
    constructor(name, sex, hobby) {
    
    
        super(name, sex);
        this.hobby = hobby;
    }
    play() {
    
    
        return this.name + this.hobby;
    }
}

var zn = new Student('张宁', '男', '玩游戏');
zn.run();
console.log(zn.say());
console.log(zn.play());

3. 严格模式

什么是严格模式

JavaScript 除正常模式,还有严格模式(strict mode)
IE10以上支持, 以下被忽略 (不支持时, 当做普通字符串)
目的:

  1. 消除Javascript语法的不合理、不严谨之处【例如变量,不声明就报错】
  2. 为未来新版本的Javascript做好铺垫 (未来指的是ES6)

3.0 开启严格模式

开启严格模式:“use strict” (只需要在顶部 - 声明一串字符串即可)

<script>
    "use strict"; // 当前脚本开启严格模式
</script>

<script>
    function fn(){
    
    
    	'use strict'; // 当前函数内, 开启严格模式
	}
</script>

3.1 严格模式规则

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

  • 变量必须 - 先声明 -再使用
  • 全局函数里的this 不再指向 window
  • 函数形参, 不能重名
"use strict"

// 1. 变量必须先声明再使用
// function myFn(){
    
    
//     var a = 10;
//     console.log(a);
// }
// myFn();

// 2. 全局函数内, this不再指向window
function myFn() {
    
    
    console.log(this); // undefined
}
myFn();
setTimeout(function () {
    
    
    console.log(this); // window
}, 1000);

// 3. 函数形参不能重名
// function fn(a, a){
    
    
//     console.log(a);
// }
// fn(100, 500);

// 总结:
// 严格模式下, 变量必须先声明再使用, 全局函数this不再指向window

以后我们都使用ES6的语法, 而非严格模式

更多规则参考:

4. 模板字符串

4.0 模板字符串_基础使用

ES6 - 新定义的字符串

作用: 简化字符串的拼接

使用: 模板字符串必须用 `` 包含 (~那个键), 可以嵌入标签, 变量的部分使用${xxx}

<body>
    <div id="one">

    </div>
    <div id="two">

    </div>
    <div id="three">

    </div>
    <script>
        // 方式1: dom创建
        var oneName = "小明";
        var oneDiv = document.getElementById("one");
        // 创建p夹着span的
        var p = document.createElement("p");
        var span = document.createElement("span");
        oneDiv.appendChild(p);
        p.appendChild(span);
        span.innerHTML = oneName;


        // 方式2: 用标签字符串拼接
        var theName = "小花"
        var twoDiv = document.getElementById("two");
        twoDiv.innerHTML = "<p><span>" + theName + "</span></p>";

        // 方式3: ES6新出的字符串
        var threeName = "小黄";
        var threeDiv = document.getElementById("three");
        threeDiv.innerHTML = `<p>
                <span>${
      
      threeName}</span>
            </p>`;
        
        

        // 总结:
        // 1. 模板字符串``, 跟单引'', 双引"", 一样都是字符串
        // 2. 模板字符串方便拼接标签/还可以回车整理格式
        // 3. 模板字符串里使用变量, 必须用${}括起来, 运行时, 会把变量的值放到这个字符串里使用
    </script>
</body>

5. class封装tab栏

了解插件如何封装和调用以及复用 - 比如之前, 使用new Swiper()

先演示如何使用定义好的插件 tab.js (new Tab())

5.0 tab栏_标签和样式

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>案例_封装tab栏_标签和样式准备</title>
        <style>
            .box {
     
     
                border: 1px solid #000;
                box-sizing: border-box;
            }
            .box * {
     
     
                box-sizing: border-box;
            }
            .box .header {
     
     
                width: 100%;
                height: 50px;
                display: flex;
                border-bottom: 1px solid #222;
            }
            .box .header span {
     
     
                transition: all 0.3s;
                background-color: #fff;
                line-height: 50px;
                text-align: center;
                cursor: pointer;
                font-size: 16px;
                border-right: 1px solid #000;
                flex: 1;
            }

            .box .header span.ac,
            .box .header span:hover {
     
     
                background-color: #ccc;
            }

            .box .main {
     
     
                width: 100%;
            }

            .box .main div {
     
     
                width: 100%;
                text-align: center;
                font-size: 60px;
                font-weight: 800;
                display: none;
            }
            .box .main div.active {
     
     
                display: block;
            }
        </style>
    </head>

    <body>
        <!-- 标签栏容器 -->
        <div class="box">
            <!-- 导航 -->
            <div class="header">
                <span class="ac">tab1</span>
                <span>tab2</span>
                <span>tab3</span>
            </div>
            <!-- 内容 -->
            <div class="main">
                <div class="active">内容1</div>
                <div>内容2</div>
                <div>内容3</div>
            </div>
        </div>
    </body>
</html>

5.1 tab栏_js创建

// 1. 标题数组元素 - 用span装起来 - 生成标签字符串 - 放到header容器内
var tabTitleArr = ["tab1", "tab2", "tab3"];
var spanStr = '';
for (var i = 0; i < tabTitleArr.length; i++) {
    
    
    // 第一个span要高亮
    spanStr += `<span class=${
      
      i == 0 ? 'ac' : ''}>${
      
      tabTitleArr[i]}</span>`;
}
var headerDiv = document.querySelector(".header");
headerDiv.innerHTML = spanStr;

// 2. 内容数组元素 - 用div装起来 - 生成标签字符串 - 放到main容器内
var contentArr = ["内容1", "内容2", "内容3"];
var divStr = ``;
for (var j = 0; j < contentArr.length; j++) {
    
    
    divStr += `<div class=${
      
      j == 0 ? 'active' : ''}>${
      
      contentArr[j]}</div>`;
}
var mainDiv = document.querySelector(".main");
mainDiv.innerHTML = divStr;

5.2 tab栏_class类定义

我们想要多个tab栏, 所以需要tab栏模板, 封装一个类

// 2. 定义类
class Tab {
    
    
    constructor(query, configObj) {
    
    
        // 3. 以前保存在var的变量上, 现在保存在对象的属性上
        // 接收容器和配置
        this.box = document.querySelector(query);
        this.headerDiv = this.box.querySelector(".header");
        this.mainDiv = this.box.querySelector(".main");
        // 导航数据和正文数据
        this.tabTitleArr = configObj.tabTitleArr;
        this.contentArr = configObj.contentArr;
        // 4. 调用初始化标签方法
        this.init();
    }
    init() {
    
    
        var spanStr = '';
        for (var i = 0; i < this.tabTitleArr.length; i++) {
    
    
            spanStr += `<span class=${
      
      i == 0 ? 'ac' : ''}>${
      
      this.tabTitleArr[i]}</span>`;
        }
        this.headerDiv.innerHTML = spanStr;

        var divStr = ``;
        for (var j = 0; j < this.contentArr.length; j++) {
    
    
            divStr += `<div class=${
      
      j == 0 ? 'active' : ''}>${
      
      this.contentArr[j]}</div>`;
        }
        this.mainDiv.innerHTML = divStr;
    }
}

// 1. 模拟swiper使用, new 类, 传入标签容器选择器, 传入配置对象
new Tab("#box", {
    
    
    tabTitleArr: ["tab1", "tab2", "tab3"],
    contentArr: ["内容1", "内容2", "<span style='color: red;'>内容3</span>"]
})

5.3 tab栏_点击切换

功能 - 点击切换

var that; // 5. 保存实例对象
class Tab {
    
    
    constructor(query, configObj) {
    
    
        that = this; // 保存实例对象
        // 接收容器和配置
        this.box = document.querySelector(query);
        this.headerDiv = this.box.querySelector(".header");
        this.mainDiv = this.box.querySelector(".main");
        // 导航数据和正文数据
        this.tabTitleArr = configObj.tabTitleArr;
        this.contentArr = configObj.contentArr;
        // 调用初始化标签方法
        this.init();
    }
    init() {
    
    
        var spanStr = '';
        for (var i = 0; i < this.tabTitleArr.length; i++) {
    
    
            // 2. 自定义属性 - 索引
            spanStr += `<span ind="${
      
      i}" class=${
      
      i == 0 ? 'ac' : ''}>${
      
      this.tabTitleArr[i]}</span>`;
        }
        this.headerDiv.innerHTML = spanStr;

        var divStr = ``;
        for (var j = 0; j < this.contentArr.length; j++) {
    
    
            divStr += `<div class=${
      
      j == 0 ? 'active' : ''}>${
      
      this.contentArr[j]}</div>`;
        }
        this.mainDiv.innerHTML = divStr;

        // 1. 事件委托 - header标签 - 点击事件
        this.headerDiv.onclick = this.toggleDiv;
    }
    toggleDiv(ev){
    
    
        // 3. 获取点击标签的索引值
        var ind = ev.target.getAttribute("ind");

        // 当前span高亮
        var spanList = that.headerDiv.querySelectorAll("span");
        for (var i = 0; i < spanList.length; i++) {
    
    
            spanList[i].className = "";
        }
        ev.target.className = "ac";

        // 4. 索引对应div出现
        var divList = that.mainDiv.querySelectorAll("div");
        for (var j = 0; j < divList.length; j++) {
    
    
            divList[j].className = "";
        }
        divList[ind].className = "active";
    }
}

new Tab("#box", {
    
    
    tabTitleArr: ["tab1", "tab2", "tab3"],
    contentArr: ["内容1", "内容2", "<span style='color: red;'>内容3</span>"]
})

5.4 tab栏_编写使用文档

好了, 功能实现后, 可以自己写一个文档, 告诉别人怎么去使用了

  • index.css - 样式文件
  • index.js - 主要功能
  • 使用插件者, 引入index.css和index.js, 然后准备标签结构, 只需要写一个new Tab()传入相关参数就有了tab栏切换的案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>tab栏_封装插件</title>
    <!-- 把案例的css代码放到css文件夹/index.css中 -->
</head>
<body>
    <!-- 引入固定结构+固定类名的标签 (id自己定义一个) -->
    <div class="box" id="box">
        <div class="header">
        </div>
        <div class="main">
        </div>
    </div>
    <!-- 把封装的class类, 放入到一个js文件夹/index.js文件中 -->
    <script src="./js/index.js"></script>
    <script>
        // 使用插件的步骤: 
        // (1): 引入插件封装的样式 (index.css)
        // (2): 引入插件的功能代码 (index.js)
        // (3): 准备标签结构(固定类名和固定结构)
        // (4): 实例化使用, 传入配置

        // 直接new 类名() 然后传入相应的参数就可以得到一个tab栏
        new Tab("#box", {
     
     
            tabTitleArr: ["体育", "军事", "娱乐"],
            contentArr: ["我是体育页面", "军事内容好啊", "这里可以放图片啊或者完整的标签结构啊, 但是注意用模板字符串哦, 假设我是娱乐新闻"]
        })
    </script>
</body>
</html>

经验值

问题:ES6类继承,出现语法报错

  • 描述:使用ES6对其它类进行继承时,语法报错如下:

在这里插入图片描述

  • 分析:
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
在访问“ this”或从派生构造函数返回之前,必须在派生类中调用super 构造函数

总结: 在继承其它构造函数之前,需要使用super关键字对父级的形参进行确认;再使用this
  • 解决:
class Father {
    
    
    say() {
    
    
        return '我是爸爸';
    }
}
class Son extends Father {
    
    
    constructor(x, y) {
    
    
        super(); // 要在使用this之前使用super()
        this.x = x;
    }
}
new Son();
  • 总结:无论父亲上面有没有属性,只要在子类中构造器函数内部操作this,就需要super 把父级相关的 属性进行确认;

面试题

事件委托以及优缺点

优点:节省内存, 给动态创建的标签绑定事件
缺点:

  1. 不冒泡,不支持。
  2. 层级过多,不建议用(建议就近委托)

class、extends是什么,super有什么作用

class - 创建类 - 语法糖

extends - 子类继承父类里的方法, 如果子类没有constructor会使用默认的

super - 子类constructor里, super()确认父类里的属性, 绑定到子类的实例对象上(还有值)

如有不足,请多指教,
未完待续,持续更新!
大家一起进步!

猜你喜欢

转载自blog.csdn.net/qq_40440961/article/details/111001736
今日推荐