1-2、前端编程思想进阶-类及ESM模块化应用(2)

184 阅读10分钟

一、面向对象(OOP:Object Oriented Programming)

1. 课堂主题&课程目标

课程主题:

  • 对象的创建
  • 工厂模式
  • new运算符
  • 构造函数
  • 原型prototype
  • 构造函数继承
  • 原型的继承
  • 原型链
  • 包装对象
  • 面相对象和面相过程编程
  • 类和对象概念

课程目标:

  • 理解面相对象思想
  • 会使用工厂模式
  • 会使用new运算符
  • 会使用构造函数
  • 理解原型
  • 理解原型链
  • 会使用继承
  • 理解类和对象
  • 包装对象

2.面向对象编程思想(面向过程 & 面向对象)

面相过程: 注重解决问题的步骤,分析问题需要的每一步,实现函数依次调用;

面相对象:

  • 是一种程序设计思想--将数据和处理数据的程序封装到对象中;
  • 特性: 抽象、 继承、封装、多态;
  • 优点:提高代码的复用性及可维护性;

image.png

对象:

  • Javascript 是一种基于对象的语言,几乎所有东西都是对象;
  • 对象创建方法:
    • 字面量创建;
    • new Object()创建;
    • Object.create()创建:创建对象的原型;

image.png

image.png

image.png

image.png

3-0.工厂模式-类

工厂模式解决了代码复用的问题; image.png

image.png

3-1.对象和类

对象:具体的某个事物(如:小明、叮当猫)

:一类事物的抽象(如:人类、猫类)

new运算符的特点

  • 1.new执行函数
  • 2.自动创建空对象;
  • 3.this绑定到空对象;
  • 4.隐式返还this;
  • 5.通过new来改造工厂模式

image.png

构造函数

  • 构造函数要通过new来调用 this指向实例化对象
  • 约定俗成构造函数首字母大写
  • 静态属性及方法
    • 静态方法里的this;

构造函数特点:

  • 1.首字母大写;

  • 2.this指向实例化对象; 【new的过程叫实例化( Person类里面每个对象)】

  • 类属性:Person.num

  • 实例属性:this.num

image.png

构造函数性能

  • 公共空间存放公共方法

image.png

构造函数原型

image.png

image.png

let zhangsan = new Person(‘张三’) console.log(zhangsan); //执行实例化
console.log(zhangsan.__proto__ === Person.prototype) // true
 
Person.prototype = {
    constructor:Person,
    hobby:function(){
        console.log("hobby");
    }

}

//原型的固有属性:(系统自带)——原型上的constructor,指向我们的构造函数Person
console.log( Person.prototype.constructor===Person); //true

let zhangsan = new Person("张三");
console.log(zhangsan.constructor===Person);//true
console.log(zhangsan.__proto__===Person.prototype);//true

let lisi = new Person("李四");

image.png

3-2.prototype原型

  • 通过new实例化出来的对象其属性和行为来自两个部分,一部分来自构造函数,另一部分来自原型。
  • 当声明一个函数的时候,同时也申明了一个原型 。
  • 原型本身是一个对象。
  • 对象属性方法查找规则;

image.png

image.png

image.png

image.png

image.png

3-3.原型 & 构造函数 & 对象关系

image.png

image.png

image.png

3-4.工厂模式对比构造函数

1.但是却没有解决对象识别的问题。 即创建的所有实例都是Object类型。(不清楚是哪个对象的实例)

2.没有原型,占用内存。

image.png

image.png

3-5.原型链

image.png

image.png

对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,称之为原型链;

1.当访问一个对象的属性或方法时,会先在对象自身上查找属性或方法是否存在, 如果存在就使用对象自身的属性或方法。 如果不存在就去创建对象的构造函数的原型对象中查找 ,依此类推,直到找到为止。 如果到顶层对象中还找不到,则返回 undefined。

2.原型链最顶层为 Object 构造函数的 prototype 原型对象,给 Object.prototype 添加属性或方法可以被除 null 和 undefined 之外的所有数据类型对象使用。

image.png

3-6.构造函数继承

call、apply、bind

function foo(*name*,*age*) {
    console.log(*this*,"姓名是"+*name*+"年龄是"+*age*);
}

// foo(); 打印的this是:windows
let obj = {
    name:"张三"
}
// foo.call(obj,"张三",20);     改变this指向为obj
// foo.apply(obj,["张三",20]);  改变this指向为obj
// foo.bind(obj)("张三",20);    

注意:foo.bind(obj)返回的是一个函数,想要看到结果还需要执行一遍直接()即可
  • 继承:子类 继承 父类所有属性和行为,父类不受影响。
  • 目的:找到类之间的共性精简代码
function Person(name){
    this.name = name;
    this.eyes = "两只";
    this.legs = "两条";
}
function Student(name){
    Person.call(this,name)
    this.className = "二班";
}
let newPerson = new Student("张三");
console.log(newPerson.className);

image.png

简单原型继承,出现影响父类的情况:

function Person(name){
    this.name = name;
    this.eyes = "两只";
    this.legs = "两条";
}
function Student(name){
    Person.call(this,name)
    this.className = "二班";
}
Student.prototype = Person.prototype  //直接赋值

3-7.原型

  • 传值和传址问题

    • 基本数据类型:Number、String、Boolean、Null、Undefined
    • 复杂数据类型/引用数据类型:Array、Date、Math、RegExp、Object、Function等
  • JOSN序列化的不足

    如果拷贝对象包含函数,或者undefined等值,此方法就会出现问题

  • 浅拷贝和深拷贝

//递归深拷贝
function deepCopy(obj){
    let newObj = Array.isArray(obj)?[]:{};
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            if(typeof obj[key] == "object"){
                newObj[key] = deepCopy(obj[key]);
            }else{
                newObj[key] = obj[key];
            }
        }
    }
    return newObj;
}

原型的继承

  • 深拷贝继承
  • 组合继承
function Dad(){
    this.name = "张三";
}
Dad.prototype.hobby = function(){
    console.log("喜欢篮球");
}
function Son(){
    Dad.call(this);
}
let F = function(){}
F.prototype = Dad.prototype;
Son.prototype = new F();
Son.prototype.constructor = Son;

let newSon = new Son();
newSon.hobby();

image.png

image.png

传值和传址

image.png

原型-深拷贝&组合继承

深拷贝继承:

image.png

image.png

组合继承:

image.png

image.png

包装对象

  • 除过null,undefined,基本类型都有自己对应的包装对象:String Number Boolean
  • 包装对象把所有的属性和方法给了基本类型,然后包装对象消失

总结

  • 1)面向对象编程
  • 2)工厂模式
  • 3)new运算符
  • 3)构造函数
  • 4)原型
  • 5)面相对象和面相过程编程
  • 6)类和对象

二、前端类及ESM模块化应用

1. 课堂目标&知识点

课堂目标:

  • 理解并能使用ES6中的类
  • 学会使用ES6中继承
  • 理解静态属性及方法
  • 会使用ES6中模块化

知识点

  • ES6中类的使用
  • ES6中继承extends、super
  • ES6静态方法和属性
  • ES6中模块化import、export
  • 王者荣耀英雄选择案例

2.ES6中的类

类的写法(ES5、ES6)

// ES5 —构造函数模拟’类‘的概念(方法写在原型上)
function Person(name){
    this.name = name;
    this.height"178cm";
    this.age20;
}
// 方法
Person.prototype.fn = function(){
    console.log("fn");
}
let zhangsan = new Person("张三"); //new 实例化
class Person{
    height="178cm";
    constructor(name,age){
        //属性
        this.name = name;
        this.age = age;
    }
    //方法
    getName(){
        console.log("姓名是:"+this.name);
    }
}
let student = new Person("张三",20);
student.getName();

------------------------------------------------------
//ES6—class语法糖,区别于ES5的写法(方法写在constructor内部)
class Person {
    //构造函数相当于上面ES5的Person
    constructor(name) { 
        this.name = name;
        this.height"178cm";
        this.age20;
    }
    fn(){
        console.log("fn");
    }

}
let zhangsan = new Person("张三");
let lisi = new Person("李四");

console.log(typeof Person); //function还是个函数
console.log(zhangsan); //打印结果如下图,__proto__内也有class Person

image.png

静态方法和属性:实例不会继承的属性和方法

class Person{
    //静态方法
    static hobby(){
        console.log("喜欢篮球");
    }
}
Person.height = "178cm"; //静态属性
Person.hobby();          //通过类来调用
console.log(Person.height);

私有成员 “#”符号

// ES5
function Person(name){
    let _weight = "130kg"//私有
    this.name = name;
    this.height"178cm";
    this.age20;
}

//ES6+,ES2020  “#”修饰符,来修饰私有属性
class Person{
    #weight = "130kg"//私有属性
    height = "178cm"//前面的public可以不写神略
    constructor(name) {
        this.name = name;
        // this.height = "178cm";
        this.age20;
    }
    getWeight(){ //通过公有方法,获取私有属性
        return this.#weight;
    }
}
console.log(zhangsan)//能看到Person的#weight,但不能打印数据 如下截图报错;
console.log(zhangsan.#weight) //打印报错,不能通过此种方法获取到;

// 公有方法获取私有属性
console.log( zhangsan.getWeight());

image.png

class Person{
    #height = "178cm";
    constructor(name){
        this.name = name;
    }
}

静态成员 static

//ES5
function Person(name) {
    this.name = name;
}
//机器—类,棉花糖-对象
Person.num10;//静态属性和类Person有关系,和zhangsan对象无关系;

let zhangsan = new Person("张三");
console.log( zhangsan.num ); //错误的 undefined
 

// ES6 static关键字
class Person{
    constructor(name){
        this.name = name;
    }
    static num = 10;
    static fn(){
        console.log("fn");
    }
}
console.log( Person.fn );
Person.fn();

image.png

静态方法和属性:实例不会继承属性和方法

// 要求一个类只能有一个实例。静态成员法

//全局变量多了,会造成变量污染;使用static方式改写 放入类内部
let instance; 
class Person{
    static instance;
    constructor(name){
        if(!Person.instance){ //instance
            Person.instancethis//instance
        }else{
            return Person.instance//instance
        }
        this.name = name;
    }
}

let zhangsan = new Person("张三");
let lisi = new Person("李四");
console.log(zhangsan,lisi)

访问器属性get、set

// 四、访问器属性
class Person{
    constructor(){
        // this.name = "张三"; 
        //换一种方式定义属性,如下方式get set
    }
    get name(){
        console.log("get");
    }
    set name(newValue){
        console.log("set" , newValue);
    }
}

let zhangsan = **new** Person();
console.log( zhangsan.name );
zhangsan.name"李四"

类的继承

class Dad{
    name = "张三";
    age = 40;
    constructor(height){
        this.height = height;
    }
    hobby(){
        console.log("喜欢篮球");
    }
}
class Son extends Dad{
    constructor(height){
        //表示父类的构造函数
        super(height);
    }
}
  • super特点
    • super只能在子类中使用,可以在constructor 及 函数或静态方法中使用;
    • 不能单独使用super;
    • super调用类似函数调用可以根据父类构造函数传参数;
    • 如果子类中没有constructor,子类会自动调取super()且传入参数到父类;
    • 子类中需要在调取super之后调用this;
// ES5写法
function Dad(){
    this.name"张三";
    this.age20;
}
function Son(){
    Dad.call(this);
}
let zhanger = new Son();
console.log(zhanger);
// ES6 extends
class Dad{
    constructor(name){
        this.name = name;
    }
    fn(){console.log("fn");}
}

class Son extends Dad{
    constructor(name){
        //调用父类的constructor,super只能存在于子类里
        super(name); 
        this.age20;
        // console.log(super) //不能单独使用,打印报错
    }
    test(){
        console.log("test");
        super.fn(); //调用父类里的函数
    }
}

抽象基类(‘类’->抽象为->‘基类’)

类可以被实例化为对象,让类继承—>基类,所以不允许直接被实例化(new)

基类:不允许直接被实例化

class AbstractPerson{
    constructor(){
        // console.log(new.target); 
        // new.target会指向AbstractPerson
        if(new.target===AbstractPerson){
          throw new Error("AbstractPerson 类不能被实例化");
        }
        this.age = 20;
    }
}
class Person extends AbstractPerson{
    constructor(name){
        super();
        this.name = name;
    }
}
let p = new Person("张三");
console.log(p);

面试题相关

小贤是一条可爱的小狗(Dog),它的叫声很好听(wow),每次看到主人的时候就会乖乖叫 一声(yelp)。从这段描述可以得到以下对象:

class Dog{
   wow(){ alert("wow"); }
   yelp(){ this.wow(); }
}

小芒和小贤一样,原来也是一条可爱的小狗,可是突然有一天疯了(MadDog),一看到 人就会每隔半秒叫一声(wow)地不停叫唤(yelp)。请根据描述,按示例的形式用代码来实现。

  • 如何实现链式调用?
  • __proto__ 和 prototype 之前有什么关系?
// 1.继承 、原型 、setInterval
class MadDog extends Dog{
    yelp(){
        setInterval(() => {
            this.wow();
        }, 500);
    }
}
let xiaomang = new MadDog();
xiaomang.yelp();
//2. 如何实现链式调用?
let p = new Promise();
// p.then().then().then().....  //返还this;返还实例化对象

class Person{
    fn(){
        console.log("fn");
        //return this;  
        //返还this(指向zhangsan),还会去实例化

        return new Person();
        //返还新的实例化对象(就不是zhangsan了)
    }
}
let zhangsan = **new** Person();
zhangsan.fn().fn().fn();
// 3. `__proto__`  和 prototype 之前有什么关系?
//实例化对象有`__proto__` ,构造函数有prototype
function Person(name)
    this.name = name;
}

// Person.prototype
// console.log( Person.__proto__);
console.dir(Person);

let obj = {};
 //不规范,不推荐(私有属性)
obj.__proto__.name"张三"; 

Object.setPrototypeOf(obj, {name:"张三"} );
console.log(obj);

image.png

3. ESM(es6模块化)

导入导出方式

<script type="module">//严格模式
function test(){
    console.log(*this*);
}
test(); //严格模式下,打印的this是undefined

import './a.js'//导出

import myfn,{*c* as e,b} from './a.js';
//以e的别名导出 (as别名导出)

import * as obj from './a.js';
//导出的全部东西 “*” 放入obj里
console.log(obj);
console.log(myfn);

// 按需加载          
document.onclickfunction(){
    // import myfn,{c as e,b} from './a.js';
    import("./a.js").then(res => {
        console.log(res);
    })
}

image.png

image.png

image.png

4. 王者荣耀选择英雄案例

  • 抽象玩家类
  • 抽象英雄类
  • 抽象技能类
  • 抽象游戏管理类
  • 模块化分各类
  • 扩展英雄及技能
  • 亚瑟
    • 契约之盾
    • 回旋打击
    • 圣剑裁决
  • 鲁班
    • 河豚手雷 S11210
    • 无敌鲨嘴炮S11220
    • 空中支援 S11230

课程总结

  • 面向对象及面向过程编程
  • ES6中类的使用
  • ES6中继承extends、super
  • ES6静态方法和属性
  • ES6中模块化import、export

下节预告

前端常用设计模式