javascript 设计模式
用对象收编变量,防止变量覆盖和变量污染
let checkObject = {
checkEmail: function () {
console.log("邮箱校验");
},
checkPhone: function () {
console.log("手机号校验");
},
checkPasswork: function () {
console.log("密码校验");
},
};
checkObject.checkEmail();
// 缺点,别人如果想要调用该方法可能会影响原来的代码
let copyObject = checkObject;
copyObject.checkEmail();
对象的另一种形式 函数
let checkObjectFn = function () {};
checkObjectFn.checkEmail = function () {
console.log("邮箱校验");
};
checkObjectFn.checkPhone = function () {
console.log("手机校验");
};
checkObjectFn.checkPasswork = function () {
console.log("密码校验");
};
// 调用
checkObjectFn.checkEmail();
真假对象
可以使用函数 返回一个对象,这样每次声明的时候都会生成一个 新对象,不会影响之前的对象方法
function checkObjectFn() {
return {
checkEmail: function () {
console.log("邮箱校验");
},
checkPhone: function () {
console.log("手机号校验");
},
checkPasswork: function () {
console.log("密码校验");
},
};
}
// 调用
checkObjectFn().checkEmail();
类也可以
函数也可以写成类的形式
下面写法缺点: 每次示例对象时,都会复制一边 this 上的属性和方法. 会造成不必要的性能浪费.
function checkObjectFn() {
this.checkEmail = function () {
console.log("邮箱检验");
};
this.checkPhone = function () {
console.log("手机检验");
};
this.checkPassword = function () {
console.log("密码检验");
};
}
let a = new checkObjectFn();
a.checkEmail();
检测类
可以将公共方法同意挂载在 原型对象上. 这样示例对象就可以通过 __proto__ 一直找,直到找到为止
let CheckObjectFn = function () {};
CheckObjectFn.prototype.checkEmail = function () {
console.log("邮箱验证");
};
CheckObjectFn.prototype.checkPhone = function () {
console.log("手机验证");
};
CheckObjectFn.prototype.checkPassword = function () {
console.log("密码验证");
};
let a = new CheckObjectFn();
a.checkEmail();
// 简化写法
let CheckObjectFn = function () {};
CheckObjectFn.prototype = {
checkEmail: function () {
console.log("邮箱验证");
},
checkPhone: function () {
console.log("手机验证");
},
checkPassword: function () {
console.log("密码验证");
},
};
let a = new CheckObjectFn();
a.checkEmail();
a.checkPhone();
a.checkPassword();
注意 :这两种 书写方式不能混合使用,要不可能出现方法覆盖,方法找不到的情况
链式调用
链式调用时 只需要在函数调用的同时,将当前
this
返回出去即可
let CheckObjectFn = function () {};
CheckObjectFn.prototype = {
checkEmail: function () {
console.log("邮箱验证");
return this;
},
checkPhone: function () {
console.log("手机验证");
return this;
},
checkPassword: function () {
console.log("密码验证");
return this;
},
};
let a = new CheckObjectFn();
a.checkEmail().checkPassword().checkPhone();
函数高级用法
我们可以在不声明变量的情况下,直接在 Function 函数的原型对象上面挂方法
Function.prototype.checkEmail = function () {
console.log("邮箱验证");
};
Function.prototype.checkPhone = function () {
console.log("手机验证");
};
Function.prototype.checkPassword = function () {
console.log("密码验证");
};
let a = function () {};
a.checkEmail();
注意:
虽然 javascript 支持上面写法
,但是为了代码的可维护性 最好不要这样书写代码.可以使用下述代码替代
Function.prototype.addMethods = function (funcname, fn) {
this[funcname] = fn;
};
let a = function () {}; // 等同于 let a = new Function()
a.addMethods("checkEmail", function () {
console.log("邮箱验证");
});
a.addMethods("checkPhone", function () {
console.log("手机验证");
});
a.addMethods("checkPassword", function () {
console.log("密码验证");
});
a.checkEmail();
a.checkPhone();
a.checkPassword();
// 链式调用
a.addMethods("checkEmail", function () {
console.log("邮箱验证");
return this;
});
a.addMethods("checkPhone", function () {
console.log("手机验证");
return this;
});
a.addMethods("checkPassword", function () {
console.log("密码验证");
return this;
});
a.checkEmail().checkPhone().checkPassword();
创建对象的安全模式
- new 关键字的作用是当前对象的 this 不停赋值. 当构造函数没有被 被实例(new)时,就会被当作一个函数执行.
- 当函数执行时 函数的作用域是全局,全局的 this 指向 window .
// 这种方法 必须通过 new 关键字进行实例对象.
function Books(price, name, category) {
this.price = price;
this.name = name;
this.category = category;
}
let book1 = Books(199, "javascript百科全书", "社科");
console.log(book1); // undefined
console.log(window.price); // 199
let book2 = new Books(66, "春秋", "历史");
console.log(book2); // Books { price: 66, name: '春秋', category: '历史' }
console.log(book2.name); // 春秋
// 函数改造 -- 创建对象安全模式
function Books(price, name, category) {
if (this instanceof Books) {
this.price = price;
this.name = name;
this.category = category;
} else {
return new Books(price, name, category);
}
}
let book1 = Books(199, "javascript百科全书", "社科");
console.log(book1.name); // javascript百科全书
继承方式
类式继承
- 类式继承的原理就是
把一个构造函数的实例对象赋值给另一个构造函数的 prototype (原型)对象.
- 例子: B 继承 A :
B.prototype = new A()
function Father() {
this.fatherValue = "father";
this.getFatherValue = function () {
return this.fatherValue;
};
}
let fa1 = new Father();
console.log(fa1.fatherValue); // father
function Children() {
this.childrenValue = "chlidren";
this.getChildrenValue = function () {
return this.childrenValue;
};
}
let ch1 = new Children();
console.log(ch1.childrenValue); // children
console.log(ch1.fatherValue); // undefined
Children.prototype = new Father();
let ch2 = new Children();
console.log(ch2.childrenValue); // children
console.log(ch2.fatherValue); // father
可以使用 instanceof 判断
前面对象是否是后面对象的实例.
- 例子: 判断 b 是否是 a 的实例 :
b instanceof a
返回值是 boolean 类型.
// instanceof
console.log(ch2 instanceof Father); // true
console.log(ch2 instanceof Children); // true
console.log(Children instanceof Father); // false
console.log(Children.prototype instanceof Father); // tr
注意: 所有对象的实例都是 Object 的实例
console.log(ch2 instanceof Object); // true
类式继承的缺点
- 如果子类修改了通过 类式继承的属性, 那么父类中的属性也会受到影响.
function Father() {
this.fatherArr = [199, "javascript设计模式", "编程"];
}
function Chlidren() {
this.ChlidrenArr = [66, "春秋", "历史"];
}
Chlidren.prototype = new Father();
let ch1 = new Chlidren();
console.log(ch1.fatherArr); // [ 199, 'javascript设计模式', '编程' ]
ch1.fatherArr.push("新增数据");
console.log(ch1.fatherArr); // [ 199, 'javascript设计模式', '编程', '新增数据' ]
构造函数继承
原理:
在子类中调用一次父类的构造函数
,并将子类的 this 传给父类.
- 构造函数继承可以解决 类式继承的缺点. 但是 构造函数继承会不断的复制当前对象的 this 违背了代码复用的初心.
function Father(id) {
this.fatherArr = [199, "javascript设计模式", "编程"];
this.id = id;
}
function Chlidren(id) {
Father.call(this, id);
}
let ch1 = new Chlidren(66);
let ch2 = new Chlidren(88);
ch1.fatherArr.push("新增数据");
console.log(ch1.fatherArr); // [ 199, 'javascript设计模式', '编程', '新增数据' ]
console.log(ch1.id); // 66
console.log(ch2.fatherArr); // [ 199, 'javascript设计模式', '编程' ]
console.log(ch2.id); // 88
组合继承
将
类式继承
和函数继承
的优点结合起来的继承方式 就是组合继承
.
缺点: 在使用构造函数继承时,调用了一次父类的构造函数; 在实现子类原型的类式继承时,又调用了一次父类的构造函数.
因此父类构造函数调用了两次
.
function Father(name) {
this.fatherArr = [199, "javascript设计模式", "编程"];
this.name = name;
}
function Chlidren(name) {
Father.call(this, name);
this.ChlidrenArr = [66, "春秋", "历史"];
}
let c1 = new Chlidren("蜻蜓队长");
c1.fatherArr.push("三国演义");
console.log(c1.name); // '蜻蜓队长'
console.log(c1.fatherArr); // [ 199, 'javascript设计模式', '编程', '三国演义' ]
let c2 = new Chlidren("铁甲小宝");
console.log(c2.fatherArr); // [ 199, 'javascript设计模式', '编程' ]
console.log(c2.name); // '铁甲小宝'
原型式继承
原型式继承 类似于 类式继承, 缺点也是相同的.
function inheritObject(o) {
// 声明一个 函数过度对象
function F() {}
// 过渡对象的原型 继承父类对象
F.prototype = o;
// 返回继承了父类对象的实例
return new F();
}
寄生式继承
原理: 就是对 原型继承进行了二次封装, 在二次封装的同时对继承的对象进行改造,添加新的属性和方法.
function inheritObject(o) {
// 声明一个 函数过度对象
function F() {}
// 过渡对象的原型 继承父类对象
F.prototype = o;
// 返回继承了父类对象的实例
return new F();
}
function createObject(o) {
// 通过原型继承 创建对象
let obj = new inheritObject(o);
obj.getName = function () {
return this.name;
};
return obj;
}
let sunObj = {
name: "孙悟空",
age: 18,
};
let copyobj = createObject(sunObj);
copyobj.name = "copy孙悟空";
console.log(copyobj.name); // copy 孙悟空
console.log(copyobj.getName()); // copy 孙悟空
console.log(sunObj.name); // 孙悟空
寄生组合式继承
原理: 寄生组合继承 和 构造函数继承的 结合,就叫做
寄生组合式继承
function inheritObject(o) {
// 声明一个 函数过度对象
function F() {}
// 过渡对象的原型 继承父类对象
F.prototype = o;
// 返回继承了父类对象的实例
return new F();
}
function inheritPrototype(fatherClass, childrenClass) {
// 复制一份 父类原型 储存在一个变量中
let p = fatherClass;
// 修改 因为继承从而导致 子类constructor 属性被修改
p.constructor = childrenClass;
childrenClass.prototype = p;
}
function Sun(name) {
this.name = name;
this.skills = ["七十二变", "大闹天宫"];
}
function Zhu(name, time) {
Sun.call(this, name);
this.time = time;
}
inheritPrototype(Sun, Zhu);
let instance1 = new Zhu("猪八戒", 2010);
let instance2 = new Zhu("孙悟空", 1999);
instance2.skills.push("偷看嫦娥洗澡");
console.log(instance1.skills);
console.log(instance2.skills);
多继承
继承多个对象的属性和方法
Object.prototype.mix = function () {
let i = 0,
len = arguments.length, // 获取传入了多少个对象
arg; // 缓存参数对象
for (; i < len; i++) {
// 缓存当前对象
arg = arguments[i];
// 遍历被继承的对象属性和方法
for (let property in arg) {
this[property] = arg[property];
}
}
};
let book1 = {
name: "三国演义",
type: "历史",
};
let book2 = {
people: "刘关张",
content: "桃园三结义",
};
let otherBook = {};
otherBook.mix(book1, book2);
console.log(otherBook); // { name: '三国演义',type: '历史',mix: [λ],people: '刘关张',content: '桃园三结义' }
多态
原理: 同一个方法 多种调用方式的行为,就叫做
多态
function add() {
let len = arguments.length;
switch (len) {
case 0:
return 10;
case 1:
return 10 + arguments[len - 1];
case 2:
return arguments[0] + arguments[1];
}
}
console.log(add());
console.log(add(6));
console.log(add(6, 6));
// 方式二 : 类形式
// 类形式
function Add() {
function zero() {
return 10;
}
function one(sum1) {
return 10 + sum1;
}
function two(sum1, sum2) {
return sum1 + sum2;
}
this.add = function () {
let len = arguments.length;
switch (len) {
case 0:
return zero();
case 1:
return one(arguments[0]);
case 2:
return two(arguments[0], arguments[1]);
}
};
}
let a = new Add();
console.log(a.add());
console.log(a.add(6));
console.log(a.add(6, 6));
设计模式
工厂模式
function Basketball() {
this.info = "篮球运动";
this.getSize = function () {
return "篮球很大";
};
}
Basketball.prototype.getNumber = function () {
return "篮球队由五个人组成";
};
function Football() {
this.info = "足球运动";
this.getSize = function () {
return "足球式是正常大小";
};
}
Football.prototype.getNumber = function () {
return "足球由是一个人组成";
};
// 运动工厂
function SportsFactory(category) {
switch (category) {
case "NBA":
return new Basketball();
case "WORLDCUP":
return new Football();
}
}
let basketball = SportsFactory("NBA");
console.log(basketball); // Basketball { info: '篮球运动', getSize: [λ] }
console.log(basketball.getNumber()); // 篮球队由五个人组成
console.log(basketball.getSize()); // 篮球很大
工厂模式变式
// 工厂模式变式
function Subject(type, content) {
if (this instanceof Subject) {
let s = new this[type](content);
return s;
} else {
return new Subject(type, content);
}
}
Subject.prototype = {
Java: function (content) {
this.content = content;
console.log("java课程" + this.content);
},
Css: function (content) {
this.content = content;
console.log("css课程" + this.content);
},
Javascript: function (content) {
this.content = content;
console.log("js课程" + this.content);
},
};
let subData = [
{ type: "Java", content: "白菜价" },
{ type: "Css", content: "进阶课程" },
{ type: "Javascript", content: "设计模式:666" },
];
for (let i = 0; i < subData.length; i++) {
console.log(Subject(subData[i].type, subData[i].content));
}
抽象类
定义: 抽象类是一种声明,但是不能使用的类,使用时会报错.
作用: 定义一个产品簇,并声明一些必备的方法,如果子类中没有重写这些方法时,就报错.
我们可以在 类中手动抛出错误去 模仿抽象类.
function Car() {
this.getPrice = function () {
return new Error("抽象方法,不能调用");
};
}
let c = new Car();
console.log(c.getPrice()); // [Error: 抽象方法,不能调用]
建造者模式
工厂模式: 创建出来的是一个对象,更注重创建的结果.
建造者模式: 可以得到创建的结果,同时也会参与到具体过程. 更类似与一个符合对象.
// 创建一个神仙类
function Immortal(param) {
// 判断是否传入参数,如果有参数就将 skill赋值给实例对象的skill属性
this.skill = (param && param.skill) || "保密";
this.hobby = (param && param.hobby) || "保密";
}
// 实例化姓名类
function Named(name) {
let that = this;
// 构造器
// 构造函数解析姓名类的 姓 和 名
(function (that, name) {
that.wholename = name;
console.log(name);
if (name && name.indexOf(" ") > -1) {
that.firstName = that.wholename.slice(0, name.indexOf(" "));
that.secondName = that.wholename.slice(name.indexOf(" "));
}
})(that, name);
}
// 实例化工作类
function Worked(work) {
let that = this;
// 构造器
// 构造函数 解析工作种类
(function (that, work) {
switch (work) {
case "Monkey":
that.work = "养马的";
that.describe = "曾在天庭养了五百匹马.";
break;
case "Pig":
that.work = "牵马的";
that.describe = "曾在天庭偷看嫦娥洗澡.";
break;
case "fish":
that.work = "拉行李的";
that.describe = "曾在天庭给玉帝倒酒.";
break;
case "monk":
that.work = "超度的";
that.describe = "曾是如来的最强小弟";
break;
}
})(that, work);
}
Worked.prototype.changWork = function (work) {
this.work = work;
};
Worked.prototype.changeDescribe = function (describe) {
this.describe = describe;
};
// 创建一个建造者
let Person = function (data) {
let { name, work } = data;
let _person = new Immortal({ skill: "七十二变" });
_person.work = new Worked(work);
_person.name = new Named(name);
return _person;
};
let monkey = new Person({ name: "孙 悟空", work: "Monkey" });
console.log(monkey.skill); // 七十二变
console.log(monkey.name.firstName); // 孙
console.log(monkey.name.secondName); // 悟空
console.log(monkey.work.work); // 养马的
console.log(monkey.work.describe); // 曾在天庭养了五百匹马.
monkey.work.changWork("齐天大圣");
monkey.work.changeDescribe("曾经大闹天宫,调戏七仙女.");
console.log(monkey.work.work); // 齐天大圣
console.log(monkey.work.describe); // 曾经大闹天宫,调戏七仙女.
原型模式
下面代码是 使用构造函数的形式实现的继承.
优点: 不会影响父类原型上的属性和方法
缺点: 每次实例的时候 都会调用两次父类实例,会造成不必要的性能消耗
function Loop(imgs, container) {
this.imgs = imgs;
this.container = container;
this.changeImgs = function () {
return "父类-changeImgs";
};
this.changeContainer = function () {
return "父类-changeContainer";
};
}
function Lefttoright(imgs, container) {
Loop.call(this, imgs, container);
}
let lf = new Loop([1, 2], "coco");
let lr = new Lefttoright([3, 4], "jack");
console.log(lf.imgs); // [1,2]
console.log(lr.imgs); // [3,4]
console.log(lf.changeImgs()); // 父类-changeImgs
console.log(lr.changeImgs()); // 父类-changeImgs
lr.changeImgs = function () {
return "子类-changeImgs";
};
console.log(lf.changeImgs()); // 父类-changeImgs
console.log(lr.changeImgs()); // 子类-changeImgs
改造
我们可以将比较消耗资源的方法放在 原型中,这样就可以避免一些不必要的性能消耗
function Loop(imgs, container) {
this.imgs = imgs;
this.container = container;
}
// 将比较消耗资源的方法 放在原型中
Loop.prototype = {
changeImgs: function () {
return "父类-changeImgs";
},
changeContainer: function () {
return "父类-changeContainer";
},
};
function Lefttoright(imgs, container) {
Loop.call(this, imgs, container);
}
Lefttoright.prototype = new Loop();
let lf = new Loop([1, 2], "coco");
let lr = new Lefttoright([3, 4], "jack");
console.log(lf.imgs); // [1,2]
console.log(lr.imgs); // [3,4]
console.log(lf.changeImgs());
console.log(lr.changeImgs());
lr.changeImgs = function () {
return "子类-changeImgs";
};
console.log(lf.changeImgs());
console.log(lr.changeImgs());
单例模式
又称单体模式,是一种只允许被实例一次的对象类
其实就是提供一个命名空间,避免变量冲突的问题.不同的程序员可能会声明相同的变量,从而导致程序出现问题.
// 程序猿1
let monkeyOne = {
name: "孙悟空",
age: 18,
};
// 程序员2
let monkeyTwo = {
name: "六耳猕猴",
age: 21,
};
结构型设计模式
外观模式
外观模式是对接口方法的外层封装,以供上层代码使用.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>study-practice</title>
</head>
<body>
<div class="box">123</div>
<script>
let domLiborary = {
dom: function (target) {
// 获取 dom 元素
document.querySelector(target);
},
css: function (target, propertyKey, value) {
// 设置元素 css样式
document.querySelector(target).style[propertyKey] = value;
},
attr: function (target, propertyKey, value) {
// 获取元素属性
document.querySelector(target)[propertyKey] = value;
},
on: function (target, type, fn) {
document.querySelector(target)["on" + type] = fn;
},
};
domLiborary.css(".box", "width", "300px");
domLiborary.css(".box", "backgroundColor", "tomato");
domLiborary.on(".box", "click", function () {
alert("domliborary -- 点击事件");
});
</script>
</body>
</html>
适配器模式
适配器模式: 就是将一个类的接口转化为另一个类的接口(属性和方法),从而解决类(对象)接口不兼容的问题. 就比如 jquery.
装饰者模式
装饰者模式:可以在不了解原有功能的基础上,进行再拓展功能并不影响原有功能.
function decorator(target, fn) {
// 获取目标元素
let targetDom = document.querySelector(target);
// 判断目标元素是否拥有点击事件
if (typeof targetDom.onclick === "function") {
// 缓存老的点击事件
let oldClick = targetDom.onclick;
// 重写点击事件
targetDom.onclick = function () {
oldClick();
fn();
};
} else {
// 如果之前没有点击事件 直接调用
targetDom.onclick = fn();
}
}
链式调用
// 链式调用:调用函数时,我们可以将当前对象返回(return),然后我们就可以继续调用当前对象
function A() {}
A.prototype = {
length: 2,
size: function () {
return this.length;
},
};
let a = new A();
console.log(a.size());
// console.log(A().size()); // Cannot read properties of undefined (reading 'size') 因为A没有返回值所以会报错
// function B() {
// return C;
// }
// let C = (B.prototype = {
// length: 2,
// size: function () {
// return this.length;
// },
// });
// console.log(B().size());
// 减少变量声明的做法 : 将对象赋值给 B的一个属性并返回,这样就可以减少变量的声明
function B() {
return B.fn;
}
B.fn = B.prototype = {
length: 2,
size: function () {
return this.length;
},
};
console.log(B().size());
状态模式
-
情景
超级玛丽实现单个动作,切换动作,组合动作
-
思路
声明一个超级玛丽类(构造函数)
定义一个状态对象 用于不同动作的映射。
定义一个动作对象用于动作的改变和动作的执行。
function MarryState() {
let _currentState = {};
let states = {
// 基础状态动作
jump() {
console.log("跳");
// return "跳";
},
shoot() {
console.log("射");
// return "射";
},
squat() {
console.log("蹲");
// return "蹲";
},
move() {
console.log("移动");
// return "移动";
},
};
let Actions = {
changeState() {
let args = arguments;
_currentState = {};
if (args.length) {
for (let i = 0; i < args.length; i++) {
_currentState[args[i]] = true;
}
return this;
}
},
goes() {
for (key in _currentState) {
states[key] && states[key]();
}
return this;
},
};
return {
change: Actions.changeState,
goes: Actions.goes,
};
}
let marry = new MarryState();
marry
.change("move")
.goes()
.change("jump")
.goes()
.goes()
.change("shoot")
.goes()
.change("squat")
.goes()
.change("jump")
.goes();
// 打印结果 移动 跳 跳 射 蹲 跳