构造器
名字吓人。结果天天使用。
- 场景: 公司员工信息录入系统、少量员工录入
const lilei = {
name: '李磊',
age: 25,
career: 'coder'
}
// 构造器方式
function User(name, age, career) {
this.name = name;
this.age = age;
this.career = career;
}
const lilei = New User('李磊', 25, 'coder');
-
程序自动地去读取数据库里面一行行的员工信息,然后把拿到的姓名、年龄职业等字段塞进User函数里,进行一个简单的调用。
-
构造器是不是将 name、age、career 赋值给对象的过程封装,确保了每个对象都具备这些属性,确保了共性的不变,同时将 name、age、career 各自的取值操作开放
简单工厂模式
- 场景: 区分员工的职业。如果是码农就写Bug。如果老板就会所。
// 构造器方式
function User(name, age, career, work) {
this.name = name;
this.age = age;
this.career = career;
this.work = work;
}
function Factory(name, age, career){
let work;
swtich(career){
case 'coder':
work = '写Bug';
break;
case 'boss':
work = '会所';
break;
default:
break;
}
return new User(name, age, career, work)
}
const pro = new Factory('pro', 18, 'book');
总结: 工厂模式的简单之处,在于它的概念相对好理解:将创建对象的过程单独封装,这样的操作就是工厂模式。同时它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new、自觉非常不爽的情况下,我们就应该思考是不是可以掏出工厂模式重构我们的代码了
单例模式
- 只有一个实例
class Modal{
static getModal(){
if(!Modal.modal){
Modal.modal = 123;
}
return Modal.modal;
}
}
const modal1 = Modal.getModal();
const modal2 = Modal.getModal();
modal1 === modal2; // true
- 场景: UI框架其中的modal只有一个实例
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>单例模式弹框</title>
</head>
<style>
#modal {
width: 200px;
height: 200px;
line-height: 200px;
text-align: center;
border-radius: 10px;
background-color: #f2f2f2;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<body>
<div class="btnBox">
<button id='open'>打开弹框</button>
<button id='close'>关闭弹框</button>
</div>
</body>
<script>
// 闭包方式
// const Modal = (function () {
// let modal = null;
// return function() {
// if (!modal){
// modal = document.createElement('div');
// modal.id = 'modal';
// modal.style.display = 'none';
// modal.innerHTML = "唯一弹窗";
// document.body.appendChild(modal);
//
// }
// return modal;
// }
// })();
// class方式
class Modal {
static getModal() {
if (!Modal.modal) {
Modal.modal = document.createElement('div');
Modal.modal.id = 'modal';
Modal.modal.style.display = 'none';
Modal.modal.innerHTML = '唯一弹窗';
document.body.appendChild(Modal.modal);
}
return Modal.modal;
}
}
// 点再多下也只是有Modal
document.getElementById('open').addEventListener('click', function () {
const modal = Modal.getModal();
modal.style.display = 'block';
});
// 点击关闭按钮隐藏模态框
document.getElementById('close').addEventListener('click', function () {
const modal = Modal.getModal();
modal.style.display = 'none';
});
</script>
</html>
原型模式
-
原型是 把所有的对象共用的属性全部放在堆内存的一个对象中(共用属性组成的对象),然后让每一个对象的__proto__存储这个(共用属性组成的对象)的地址。而这个共用属性就是原型。原型出现的目的就是为了减少不必要的内存消耗。
-
原型链就是对象通过__proto__向当前实例所属类的原型上查找属性或方法的机制,如果找到Object的原型上还是没有找到想要的属性或者是方法则查找结束,最终会返回undefined,终点是null。
-
原型模式不仅是一种设计模式,它还是一种编程范式,是 JavaScript 面向对象系统实现的根基。
function Dog() {
}
// 原型增加属性和方法
Dog.prototype.name = 'pro';
Dog.prototype.eat = () => {
console.log(123);
}
装饰器模式
只添加,不修改就是装饰器模式了
- 场景: 初始需求是每个业务中的按钮在点击后都弹出「您还未登录哦」的弹框。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>按钮点击需求1.0</title>
</head>
<style>
#modal {
height: 200px;
width: 200px;
line-height: 200px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 1px solid black;
text-align: center;
}
</style>
<body>
<button id='open'>点击打开</button>
<button id='close'>关闭弹框</button>
</body>
<script>
// 弹框创建逻辑,这里我们复用了单例模式面试题的例子
const Modal = (function() {
let modal = null
return function() {
if(!modal) {
modal = document.createElement('div')
modal.innerHTML = '您还未登录哦~'
modal.id = 'modal'
modal.style.display = 'none'
document.body.appendChild(modal)
}
return modal
}
})()
// 点击打开按钮展示模态框
document.getElementById('open').addEventListener('click', function() {
// 未点击则不创建modal实例,避免不必要的内存占用
const modal = new Modal()
modal.style.display = 'block'
})
// 点击关闭按钮隐藏模态框
document.getElementById('close').addEventListener('click', function() {
const modal = document.getElementById('modal')
if(modal) {
modal.style.display = 'none'
}
})
</script>
</html>
- 突然修改需求:弹框被打开后把按钮的文案改为“快去登录”,同时把按钮置灰。存在几百按钮且同时他们不是组件且有复杂业务情况下。不去关心它现有的业务逻辑是啥样的。对它已有的功能做个拓展,只关心拓展出来的那部分新功能如何实现
为了不被已有的业务逻辑干扰,当务之急就是将旧逻辑与新逻辑分离,把旧逻辑抽出去:
// 将展示Modal的逻辑单独封装
function openModal() {
const modal = new Modal()
modal.style.display = 'block'
}
// 新增逻辑
// 按钮文案修改逻辑
function changeButtonText() {
const btn = document.getElementById('open')
btn.innerText = '快去登录'
}
// 按钮置灰逻辑
function disableButton() {
const btn = document.getElementById('open')
btn.setAttribute("disabled", true)
}
// 新版本功能逻辑整合
function changeButtonStatus() {
changeButtonText()
disableButton()
}
document.getElementById('open').addEventListener('click', function() {
openModal()
changeButtonStatus()
})
- 使用ES6面向对象写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>按钮点击需求1.0</title>
</head>
<style>
#modal {
height: 200px;
width: 200px;
line-height: 200px;
position: fixed;
left: 50%;
top: 50%;
border-radius: 10px;
transform: translate(-50%, -50%);
border: 1px solid black;
text-align: center;
}
</style>
<body>
<button id='open'>点击打开</button>
<button id='close'>关闭弹框</button>
</body>
<script>
// 弹框创建逻辑,这里我们复用了单例模式面试题的例子
const Modal = (function() {
let modal = null;
return function() {
if(!modal) {
modal = document.createElement('div');
modal.innerHTML = '您还未登录哦~';
modal.id = 'modal';
modal.style.display = 'none';
document.body.appendChild(modal)
}
return modal
}
})();
// 定义打开按钮
class OpenButton {
onClick() {
const modal = new Modal();
modal.style.display = 'block';
}
}
// 定义按钮对应的装饰器
class Decorator{
// 将按钮传入
constructor(open_button) {
this.open_button = open_button;
}
onClick(){
this.open_button.onClick();
this.changeButtonStatus();
}
changeButtonStatus() {
this.disableButton();
this.changeButtonText();
}
disableButton() {
const btn = document.getElementById('open');
btn.setAttribute('disabled', true);
}
changeButtonText() {
const btn = document.getElementById('open');
btn.innerText = '快去登录'
}
}
// 点击打开按钮展示模态框
document.getElementById('open').addEventListener('click', function() {
// 未点击则不创建modal实例,避免不必要的内存占用
const openButton = new OpenButton();
const decorator = new Decorator(openButton);
decorator.onClick();
});
// 点击关闭按钮隐藏模态框
document.getElementById('close').addEventListener('click', function() {
const modal = document.getElementById('modal');
if(modal) {
modal.style.display = 'none'
}
})
</script>
</html>
适配模式
适配器模式通过把一个类的接口变换成客户端所期待的另一种接口,可以帮我们解决不兼容的问题。
- 场景: iPhoneX没有圆头耳机孔、转接头是个适配模式。
把一个类
(iPhone X)的接口
(方形)变换成客户端
(用户)所期待的另一种接口
(圆形)
- axios能在网页和Nodejs不同环境使用就是使用了适配模式
代理模式
代理模式,式如其名——在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式
![image-20210121162225864](/Users/DJVue/Library/Application Support/typora-user-images/image-20210121162225864.png)
代理服务器 = 代理模式
![image-20210121162237935](/Users/DJVue/Library/Application Support/typora-user-images/image-20210121162237935.png)
- 事件代理也是代理模式的一种
事件代理,可能是代理模式最常见的一种应用方式,也是一道实打实的高频面试题。它的场景是一个父元素下有多个子元素。
需求: 点击每个 a 标签,都可以弹出“我是xxx”这样的提示。比如点击第一个 a 标签,弹出“我是链接1号”这样的提示
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件代理</title>
</head>
<body>
<div id="father">
<a href="#">链接1号</a>
<a href="#">链接2号</a>
<a href="#">链接3号</a>
<a href="#">链接4号</a>
<a href="#">链接5号</a>
<a href="#">链接6号</a>
</div>
<script>
const father = document.getElementById('father');
father.addEventListener('click',(e) => {
if (e.target.tagName === 'A'){
e.preventDefault();
alert(`我是${e.target.innerText}`);
}
})
</script>
</body>
</html>
策略模式
需求:
- 当价格类型为“预售价”时,满 100 - 20,不满 100 打 9 折
- 当价格类型为“大促价”时,满 100 - 30,不满 100 打 8 折
- 当价格类型为“返场价”时,满 200 - 50,不叠加
- 当价格类型为“尝鲜价”时,直接打 5 折
转成字段
预售价 - pre
大促价 - onSale
返场价 - back
尝鲜价 - fresh
当初的我对prd处理为:
// 询价方法,接受价格标签和原价为入参
function askPrice(tag, originPrice) {
// 处理预热价
if(tag === 'pre') {
if(originPrice >= 100) {
return originPrice - 20
}
return originPrice * 0.9
}
// 处理大促价
if(tag === 'onSale') {
if(originPrice >= 100) {
return originPrice - 30
}
return originPrice * 0.8
}
// 处理返场价
if(tag === 'back') {
if(originPrice >= 200) {
return originPrice - 50
}
return originPrice
}
// 处理尝鲜价
if(tag === 'fresh') {
return originPrice * 0.5
}
}
现在的我会采用策略模式:
// 定义一个询价处理器对象
const priceProcessor = {
pre(originPrice) {
if (originPrice >= 100) {
return originPrice - 20;
}
return originPrice * 0.9;
},
onSale(originPrice) {
if (originPrice >= 100) {
return originPrice - 30;
}
return originPrice * 0.8;
},
back(originPrice) {
if (originPrice >= 200) {
return originPrice - 50;
}
return originPrice;
},
fresh(originPrice) {
return originPrice * 0.5;
},
};
// 询价函数
function askPrice(tag, originPrice) {
return priceProcessor[tag](originPrice)
}
// 如要增加需求
priceProcessor.newUser = function (originPrice) {
if (originPrice >= 100) {
return originPrice - 50;
}
return originPrice;
}
一个函数只做一件事、遇到 Bug 时,就可以做到“头痛医头,脚痛医脚”,而不必在庞大的逻辑海洋里费力去定位到底是哪块不对。
策略模式就是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
状态模式
原理跟策略模式相似。故不展开
观察者模式
观察者模式,是所有 JavaScript 设计模式中使用频率最高,面试频率也最高的设计模式,所以说它十分重要——如果我是面试官,考虑到面试时间有限、设计模式这块不能多问,我可能在考查你设计模式的时候只会问观察者模式这一个模式。该模式的权重极高,我们此处会花费两个较长的章节把它掰碎嚼烂了来掌握。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
// 定义发布者类
class Publisher {
constructor() {
this.observers = []
console.log('Publisher created')
}
// 增加订阅者
add(observer) {
console.log('Publisher.add invoked')
this.observers.push(observer)
}
// 移除订阅者
remove(observer) {
console.log('Publisher.remove invoked')
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
}
// 通知所有订阅者
notify() {
console.log('Publisher.notify invoked')
this.observers.forEach((observer) => {
observer.update(this)
})
}
}
// 定义订阅者类
class Observer {
constructor() {
console.log('Observer created')
}
update() {
console.log('Observer.update invoked')
}
}
- 面试题: Vue双向绑定
// observe方法遍历并包装对象属性
function observe(target) {
// 若target是一个对象,则遍历它
if(target && typeof target === 'object') {
Object.keys(target).forEach((key)=> {
// defineReactive方法会给目标属性装上“监听器”
defineReactive(target, key, target[key])
})
}
}
// 定义defineReactive方法
function defineReactive(target, key, val) {
// 属性值也可能是object类型,这种情况下需要调用observe进行递归遍历
const dep = new Dep()
observe(val)
// 为当前属性安装监听器
Object.defineProperty(target, key, {
// 可枚举
enumerable: true,
// 不可配置
configurable: false,
get: function () {
return val;
},
// 监听器函数
set: function (value) {
// 通知所有订阅者
dep.notify()
}
});
}
// 定义订阅者类Dep
class Dep {
constructor() {
// 初始化订阅队列
this.subs = []
}
// 增加订阅者
addSub(sub) {
this.subs.push(sub)
}
// 通知订阅者(是不是所有的代码都似曾相识?)
notify() {
this.subs.forEach((sub)=>{
sub.update()
})
}
}
观察者模式与发布-订阅模式的区别是什么?
所有的开发者拉了一个群,直接把需求文档丢给每一位群成员,这种发布者直接触及到订阅者的操作,叫观察者模式。但如果把需求文档上传到了公司统一的需求平台上,需求平台感知到文件的变化、自动通知了每一位订阅了该文件的开发者,这种发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式。
迭代器模式
迭代器模式是设计模式中少有的目的性极强的模式: 遍历
- 任何数据结构只要具备Symbol.iterator属性,就可以被遍历
// 编写一个迭代器生成函数
function *iteratorGenerator() {
yield '1号选手'
yield '2号选手'
yield '3号选手'
}
const iterator = iteratorGenerator()
iterator.next()
iterator.next()
iterator.next()
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!