JavaScript设计模式(一):面向对象编程 - 继承

灵活的语言-JavaScript

实现一个验证表单的功能,需要验证用户名、邮箱、密码

用对象收编变量

let CheckObject = {
    
    
    checkName: function () {
    
    
        // 验证姓名
    },
    checkEmail: function () {
    
    
        // 验证邮箱
    },
    checkPassword: function () {
    
    
        // 验证密码
    },
}

对象的另一种形式(函数对象)

  • 缺点:
    • 对象类不能复制一份(或者说这个对象类在用new关键字创建新的对象时,新创建的对象是不能继承这些方法的)
let CheckObject = function () {
    
    
};
CheckObject.checkName = function () {
    
    
    // 验证姓名
}
CheckObject.checkEmail = function () {
    
    
    // 验证邮箱
}
CheckObject.checkPassword = function () {
    
    
    // 验证密码
}

真假对象(闭包和类)

实现复制功能,人手一份

// 闭包 - 内部函数返回到外部
let CheckObject = function () {
    
    
    return {
    
    
        checkName: function () {
    
    
            // 验证姓名
        },
        checkEmail: function () {
    
    
            // 验证邮箱
        },
        checkPassword: function () {
    
    
            // 验证密码
        },
    }
}
let a = CheckObject();
a.checkName();
// 类 - 对象的实例化
let CheckObject = function () {
    
    
    this.checkName = function () {
    
    
        // 验证姓名
    }
    this.checkEmail = function () {
    
    
        // 验证邮箱
    }
    this.checkPassword = function () {
    
    
        // 验证密码
    }
}
let a = new CheckObject();
a.checkName();

一个检测类

  • 以上方法通过new关键字创建对象时,新创建额对象都会对类的this上的属性进行复制,所以这些新创建的对象都会有自己的一套方法,但缺点是消耗大,因为每次都需要复制一套方法

将方法创建在类的原型上,这样调用的方法就都会是同一个,减小消耗

let CheckObject = function () {
    
    
};
CheckObject.prototype.checkName = function () {
    
    
    // 验证姓名
    return this;
}
CheckObject.prototype.checkEmail = function () {
    
    
    // 验证邮箱
    return this;
}
CheckObject.prototype.checkPassword = function () {
    
    
    // 验证密码
    return this;
}
let a = new CheckObject();
a.checkName().checkEmail().checkPassword();

函数的祖先

抽象出一个可以添加方法功能的方法

Function.prototype.addMethod = function (name, fn) {
    
    
    this[name] = fn;
}

let methods = function () {
    
    
}; // 或者 let methods = new Function();

methods.addMethod('checkName', function () {
    
    
    // 验证姓名
});
methods.addMethod('checkEmail', function () {
    
    
    // 验证邮箱
});
methods.addMethod('checkPassword', function () {
    
    
    // 验证密码
});

链式添加方法功能并链式调用(以下两个方法只能为自身添加方法)

在这里插入图片描述

// 链式添加-方式一 (链式调用)
Function.prototype.addMethod = function (name, fn) {
    
    
    this[name] = fn;
}

let methods = function () {
    
    
}; // 或者 let methods = new Function();

methods.addMethod('checkName', function () {
    
    
    // 验证姓名
    console.log('验证姓名');
    return this;
});
methods.addMethod('checkEmail', function () {
    
    
    // 验证邮箱
    console.log('验证邮箱');
    return this;
});
methods.addMethod('checkPassword', function () {
    
    
    // 验证密码
    console.log('验证密码');
    return this;
});
methods.checkName().checkEmail().checkPassword(); // 验证姓名 验证邮箱 验证密码
// 链式添加-方式二 (方式一的改进版本) 存在消耗问题
Function.prototype.addMethod = function (name, fn) {
    
    
    this[name] = fn;
    return this;
}

let methods = function () {
    
    
}; // 或者 let methods = new Function();

methods.addMethod('checkName', function () {
    
    
    // 验证姓名
    console.log('验证姓名');
    return this;
}).addMethod('checkEmail', function () {
    
    
    // 验证邮箱
    console.log('验证邮箱');
    return this;
}).addMethod('checkPassword', function () {
    
    
    // 验证密码
    console.log('验证密码');
    return this;
});
methods.checkName().checkEmail().checkPassword(); // 验证姓名 验证邮箱 验证密码

改进:在原型上添加方法功能的方法

在这里插入图片描述

// 链式添加-方式三 (方式二的改进版本) 减小消耗
Function.prototype.addMethod = function (name, fn) {
    
    
    this.prototype[name] = fn;
    return this;
}

let Methods = function () {
    
    
}; // 或者 let methods = new Function();

Methods.addMethod('checkName', function () {
    
    
// 验证姓名
    console.log('验证姓名');
    return this;
}).addMethod('checkEmail', function () {
    
    
// 验证邮箱
    console.log('验证邮箱');
    return this;
}).addMethod('checkPassword', function () {
    
    
// 验证密码
    console.log('验证密码');
    return this;
});

let m = new Methods();
m.checkName().checkEmail().checkPassword(); // 验证姓名 验证邮箱 验证密码

写的都是看到的-面向对象编程

创建一个类(三种方式)

方式一:函数this创建方式

在这里插入图片描述

// 1.函数this创建方式
function Book(id, bookName, price) {
    
    
    this.id = id;
    this.bookName = bookName;
    this.price = price;
}

let book = new Book('abc', 'JS设计模式', 123.12);
console.log(book);

方式二:class-this创建方式

在这里插入图片描述

// 2.class-this创建方式
class Book {
    
    
    constructor(id, bookName, price) {
    
    
        this.id = id;
        this.bookName = bookName;
        this.price = price;
    }
}

let book = new Book('abc', 'JS设计模式', 123.12);
console.log(book);

方式三:原型创建方式 (setBookInfo方法在原型上)

在这里插入图片描述

// 3.原型创建方式 setBookInfo方法在原型上
let Book = function () {
    
    
};
Book.prototype.setBookInfo = function (id, bookName, price) {
    
    
    this.id = id;
    this.bookName = bookName;
    this.price = price;
}
let book = new Book();
book.setBookInfo('abc', 'JS设计模式', 123.12);
console.log(book);

图解

在这里插入图片描述

类的属性和方法

  • 属性和方法
    • 私有属性
    • 私有方法
    • 特权方法
    • 对象公有属性
    • 对象公有方法
    • 构造器
    • 类静态公有属性
    • 类静态公有方法

在这里插入图片描述

let Person = function (id, name, age) {
    
    
    // 私有属性
    let topSecret = '私密';

    // 私有方法
    function secretFn() {
    
    
    }

    // 特权方法
    // 		能访问到自身的一些属性和方法,所以被称为特权方法;
    // 		在对象创建时可以实例化对象的一些属性,又可被用作构造器;
    this.setName = function (n) {
    
    
        this.name = n;
    };
    this.setAge = function (a) {
    
    
        this.age = a;
    };

    // 对象公有属性
    this.id = id;

    // 对象公有方法(让他说出自己的秘密)
    this.publicFn = function () {
    
    
        console.log(topSecret);
    };

    // 构造器
    this.setName(name);
    this.setAge(age);
}

// 类静态公有属性(new过的对象不能访问)
Person.num = 10;
// 类静态公有方法(new过的对象不能访问)
Person.say = function () {
    
    
};

Person.prototype = {
    
    
    // 公有属性
    publicProp: '原型上的属性!',
    // 公有方法
    publicMethod: function () {
    
    
    }
}

let lee = new Person('abc', 'ProsperLee', 18);

console.log('Person', {
    
    Person});
console.log('lee', lee);

lee.publicFn(); // 私密

lee.publicProp; // 原型上的属性!

Person.num; // 10

lee.say; // undefined

在类的外部通过.语法定义的属性和方法和在类的外部通过prototype定义的属性和方法的区别:

function Person() {
    
    
    // this.say1(); // Error TypeError: this.say1 is not a function
    this.say2(); // Hello Lee!!!
}

Person.say1 = function () {
    
    
    console.log('Hello World!!!');
};

Person.prototype = {
    
    
    say2() {
    
    
        console.log('Hello Lee!!!');
    }
};

let person = new Person();

// person.say1(); // Error TypeError: person.say1 is not a function
person.say2(); // Hello Lee!!!
  • 区别:
    • 在类的外部通过.语法定义的属性和方法:
      • 不可通过 this 或者 new 的实例对象进行访问;
      • 可通过类进行调用 Person.say1();;
    • 在类的外部通过prototype定义的属性和方法:
      • 可通过 this 或者 new 的实例对象进行访问;
      • 不可通过类进行调用 Person.say2(); // Error;
      • 可通过 Person.prototype.方法 进行调用;

通过闭包来实现类的静态变量定义,使之更像一个整体

在这里插入图片描述

// 利用闭包实现
let Book = (function () {
    
    

    // 静态私有变量
    let bookNum = 0;

    // 静态私有方法
    let checkBook = function (name) {
    
    
    };

    // 创建类
    function _book(newId, newName, newPrice) {
    
    

        // 私有变量
        let name, price;

        // 私有方法
        function checkID(id) {
    
    
        }

        // 特权方法
        this.getName = () => name;
        this.getPrice = () => price;
        this.setName = function (n) {
    
    
            name = n;
        };
        this.setPrice = function (p) {
    
    
            price = p;
        };

        // 公有属性
        this.id = newId;
        name = newName;
        price = newPrice;

        // 公有方法
        this.copy = function () {
    
    
        };

        bookNum++;

        if (bookNum > 100) {
    
    
            throw new Error('我们仅出版100本书!!!');
        }

        // 构造器
        this.setName(name);
        this.setPrice(price);
    }

    // 构造原型
    _book.prototype = {
    
    
        // 静态公有属性
        isJSBook: true,
        // 静态公有方法
        display() {
    
    
        },
    };

    // 返回类
    return _book;

})();


let book = new Book('abc', 'JS设计模式', '12.34');

console.log('Book', {
    
    Book});

console.log('book', book);
book.getName(); // 'JS设计模式'
book.getPrice(); // '12.34'

忘记 new 怎么办?

let Book = function (id, bookName, price) {
    
    
    this.id = id;
    this.bookName = bookName;
    this.price = price;
}
let book = Book('abc', '《JS设计模式》', 12.34);
console.log(book);               // undefined
console.log(window.id);          // abc              this指向了window
console.log(window.bookName);    // 《JS设计模式》     this指向了window
console.log(window.price);       // 12.34            this指向了window

解决办法:

let Book = function (id, bookName, price) {
    
    
    // 判断this是否是当前这个对象(如果是说明是用new创建的)
    if (this instanceof Book) {
    
    
        this.id = id;
        this.bookName = bookName;
        this.price = price;
    } else {
    
     // 否则重新创建这个对象
        return new Book(id, bookName, price);
    }
}
let book = Book('abc', '《JS设计模式》', 12.34);
console.log(book);                  // Book {id: 'abc', bookName: '《JS设计模式》', price: 12.34}
console.log(window.id);             // undefined
console.log(window.bookName);       // undefined
console.log(window.price);          // undefined

子类的原型对象-类式继承

在这里插入图片描述

在这里插入图片描述

// 父类
function SuperClass() {
    
    
    this.superValue = true;
}

// 为父类添加共有方法
SuperClass.prototype.getSuperValue = function () {
    
    
    return this.superValue;
};

// 子类
function SubClass() {
    
    
    this.subValue = false;
}

// 继承父类
SubClass.prototype = new SuperClass();
// 为子类添加共有方法
SubClass.prototype.getSubValue = function () {
    
    
    return this.subValue;
};

let superClass = new SuperClass();
let subClass = new SubClass();

console.log(superClass, subClass);
  • instanceof 通过判断对象的 prototype链 来确定这个对象是否是某个类的实例,并不是表示两者的继承关系
  • 所有对象都是 Object 的实例
console.log(superClass instanceof SuperClass);          // true
console.log(subClass instanceof SubClass);              // true

console.log(subClass instanceof SuperClass);            // true
console.log(SubClass instanceof SuperClass);            // false

console.log(SubClass.prototype instanceof SuperClass);  // true		契合以上判断条件
console.log(SubClass instanceof Object);  				// true		契合以上判断条件

类式继承的缺点:

// 父类
function Father() {
    
    
    this.str = 'Lee';
    this.data = ['Lee'];
}

// 子类
function Son() {
    
    
}

// 继承父类
Son.prototype = new Father();

let instance1 = new Son();
let instance2 = new Son();

console.log(instance1.data, instance2.data);  // ['Lee'] ['Lee']

instance1.str = 'Tom';
instance1.data.push(18);

console.log(instance1.str, instance2.str);    // Tom Lee
console.log(instance1.data, instance2.data);  // (2)['Lee', 18] (2)['Lee', 18]
  1. 当一个子类的实例``更改子类原型从父类构造函数继承来的共有属性就会直接影响到其他子类
    • 比如: 修改 instance1 原型上继承来自父类的引用类型的值时, instance2 获取到的值也被改变了
  2. 无法在实例化子类的同时向父类传递参数
    • 比如: new Son(参数); 的时候没发把参数传递给父类,因为子类实现的继承是靠其原型等于父类的实例化实现的

创建即继承-构造函数继承

利用 call 改变函数作用环境

在这里插入图片描述

// 父类
function Father(id) {
    
    
    // 值类型公有属性
    this.id = id;
    // 引用类型公有属性
    this.data = ['Lee', 'Tom'];
}

// 父类原型方法
Father.prototype.showData = function () {
    
    
    console.log(this.data);
}

// 子类
function Son(id) {
    
    
    // 继承父类
    Father.call(this, id);
}

let instance1 = new Son('id-001');
let instance2 = new Son('id-002');

instance1.data.push('张三');

instance1.id;       // 'id-001'
instance1.data;     // ['Lee', 'Tom', '张三']

instance2.id;       // 'id-002'
instance2.data;     // ['Lee', 'Tom']

instance1.showData(); // Error TypeError: instance1.showData is not a function
instance2.showData(); // Error TypeError: instance2.showData is not a function

构造函数继承缺点:

  • 无法调用父类原型上的属性和方法(无法继承来自原型上的属性和方法)
    • 比如: instance1.showData();instance2.showData(); 会报错

将优点为我所用-组合继承

在这里插入图片描述

// 父类
function Father(fatherName) {
    
    
    // 值类型公有属性
    this.fatherName = fatherName;
    // 引用类型公有属性
    this.friends = ['Lee', 'Tom'];
}

Father.prototype.getFatherName = function () {
    
    
    console.log(this.fatherName);
};

// 子类
function Son(fatherName, myName) {
    
    
    // 构造函数式继承类
    Father.call(this, fatherName);
    // 子类中的公有属性
    this.myName = myName;
}

// 类式继承 - 子类继承父类
Son.prototype = new Father();

Son.prototype.getMyName = function () {
    
    
    console.log(this.myName);
};

console.log({
    
    Father}, {
    
    Son});

let son = new Son('汤姆', 'Tom');

console.log(son);

组合 类式和构造函数式 继承的缺点:

  • 父类的构造函数执行了两遍
    • 使用构造函数式继承时执行了一次父类的构造函数
    • 实现子类原型的类式继承时又调用了一遍父类的构造函数

洁净的继承者-原型式继承

在这里插入图片描述

// 原型式继承
function inheritObject(o) {
    
    
    // 声明一个 过渡函数对象
    function F() {
    
     }
    // 过渡对象 的 原型 继承 父对象
    F.prototype = o;
    // 返回过度对象的一个实例,该实例的原型继承了父对象
    return new F();
}

let friends = ['Lee', 'Tom'];

let obj1 = inheritObject({
    
     getName() {
    
     console.log('obj1', this.name); } });
obj1.name = 'Lee';
obj1.friends = friends;

let obj2 = inheritObject({
    
     getName() {
    
     console.log('obj2', this.name); } });
obj2.name = 'Tom';
obj2.friends = friends;

原型式继承的缺点:

在这里插入图片描述

obj1.friends.push('张三'); // friends.push('张三');
console.log('【obj1】', obj1, '【obj2】', obj2);
  • 当一个子类的实例``更改子类从父类构造函数继承来的共有属性就会直接影响到其他子类的实例
    • 比如: 修改 obj1 上继承来自父类的引用类型的值时, obj2 获取到的值也被改变了

如虎添翼-寄生式继承(对原型式继承的增强)

其实寄生式继承就是对原型继承的第二次封装,并且在这第二次封装过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法而且还添加新的属性和方法

在这里插入图片描述

// 基类
let userInfo = {
    
     name: 'Lee', age: 18 };
let friends = ['Lee', 'Tom'];

// 原型式继承
function inheritObject(o) {
    
    
    // 声明一个 过渡函数对象
    function F() {
    
     }
    // 过渡对象 的 原型 继承 父对象
    F.prototype = o;
    // 返回过度对象的一个实例,该实例的原型继承了父对象
    return new F();
}

function createObj(obj) {
    
    
    // 原型式继承
    let o = inheritObject(obj);
    // 拓展新对象
    o.friends = friends;
    o.getName = function () {
    
    
        console.log(this.name);
    }
    // 返回拓展后的新对象
    return o;
}

let obj1 = createObj(userInfo);
let obj2 = createObj(userInfo);

obj1.friends.push('张三');

console.log('【obj1】', obj1, '【obj2】', obj2);

寄生式继承缺点:

  • 与原型式继承相同

终极继承者-寄生组合式继承(寄生式继承构造函数继承的组合)

解决了子类实例化 修改公有属性 影响其他实例化的对象 的问题

在这里插入图片描述

// 原型式继承
function inheritObject(o) {
    
    
    // 声明一个 过渡函数对象
    function F() {
    
     }
    // 过渡对象 的 原型 继承 父对象
    F.prototype = o;
    // 返回过度对象的一个实例,该实例的原型继承了父对象
    return new F();
}

/**
 * 寄生组合式继承 - 寄生式继承,继承原型
 * @param {class} sonClass 子类
 * @param {class} fatherClass 父类
 */
function inheritPrototype(sonClass, fatherClass) {
    
    
    // 赋值一份父类的原型副本保存在变量中
    let p = inheritObject(fatherClass.prototype);
    // 修正因为重写子类原型导致子类的constructor属性被修改
    p.constructor = sonClass;
    // 设置子类的原型
    sonClass.prototype = p;
}

// 父类
function Father(fatherName) {
    
    
    this.fatherName = fatherName;
    this.friends = ['Lee', 'Tom'];
}

Father.prototype.getFatherName = function () {
    
    
    console.log(this.fatherName);
}

// 子类
function Son(fatherName, sonName) {
    
    
    Father.call(this, fatherName);
    this.sonName = sonName;
}

// 寄生式继承父类原型
inheritPrototype(Son, Father);

Son.prototype.getSonName = function () {
    
    
    console.log(this.sonName);
}

let son1 = new Son('汤姆', 'Tom');
let son2 = new Son('张三', 'ZhangSan');

son1.getFatherName();   // 汤姆
son1.getSonName();      // Tom

son1.friends.push('张三');

console.log('【son1】', son1, '【son2】', son2);

原理图

在这里插入图片描述

老师不止一位-多继承

单继承

在这里插入图片描述

// 单继承 属性复制继承(此示例使用的是浅克隆)
var extend = function (target, source) {
    
    
    // 遍历源对象中的属性
    for (var property in source) {
    
    
        // 将源对象中的属性复制到目标对象中
        target[property] = source[property];
    }
    // 返回目标对象
    return target;
};

var father = {
    
    
    money: 1E10,
    friends: ['Lee', 'Tom']
}
var son = {
    
    
    name: '张三'
}

// son 继承 father
extend(son, father);

son.friends.push('Lucy');

son.money = 10;

son.name = '小明';

多继承 (方式一)

在这里插入图片描述

// 多继承 属性复制  
var mix = function () {
    
    
    var i = 1,                      // 从第二个参数起为被继承的对象
        len = arguments.length,     // 获取参数长度
        target = arguments[0],      // 第一个对象为目标对象
        arg;                        // 缓存参数对象
    // 遍历被继承的对象
    for (; i < len; i++) {
    
    
        // 缓存当前对象
        arg = arguments[i];
        // 遍历被继承对象中的属性
        for (var property in arg) {
    
    
            // 将被继承对象中的属性复制到目标对象中
            target[property] = arg[property];
        }
    }

    // 返回目标对象
    return target;
};

let parent1 = {
    
     money: 1E10 };
let parent2 = {
    
     friends: ['Lee', 'Tom'] };
let parent3 = {
    
     hobbies: ['躺着'] };
let son = {
    
     name: '小明' };

mix(son, parent1, parent2, parent3);

console.log(son);

多继承 (方式二)

在这里插入图片描述

Object.prototype.mix = function () {
    
    
    var i = 0,                      // 从第一个参数起为被继承的对象
        len = arguments.length,     // 获取参数长度
        arg;                        // 缓存参数对象
    // 遍历被继承的对象
    for (; i < len; i++) {
    
    
        // 缓存当前对象
        arg = arguments[i];
        // 遍历被继承对象中的属性
        for (var property in arg) {
    
    
            // 将被继承对象中的属性复制到目标对象中
            this[property] = arg[property];
        }
    }
}

let parent1 = {
    
     money: 1E10 };
let parent2 = {
    
     friends: ['Lee', 'Tom'] };
let parent3 = {
    
     hobbies: ['躺着'] };
let son = {
    
     name: '小明' };
son.mix(parent1, parent2, parent3);
console.log(son);

多种调用方式-多态

多态,就是同一个方法多种调用方式

function Add() {
    
    

    // 无参数算法
    function zero() {
    
    
        return 10;
    }
    
    // 一个参数算法
    function one(num) {
    
    
        return 10 + num;
    }
    
    // 两个参数算法
    function two(num1, num2) {
    
    
        return num1 + num2;
    }
    
    // 相加共有方法
    this.add = function () {
    
    
        var arg = arguments,
            // 获取参数长度
            len = arg.length;
        switch (len) {
    
    
            // 如果没有参数
            case 0:
                return zero();
            // 如果只有一个参数
            case 1:
                return one(arg[0]);
            // 如果有两个参数
            case 2:
                return two(arg[0], arg[1]);
        }
    }
}
// 实例化类
var A = new Add();
//测试
console.log(A.add());       // 10
console.log(A.add(5));      // 15
console.log(A.add(6, 7));   // 13

猜你喜欢

转载自blog.csdn.net/weixin_43526371/article/details/127241878