面向对象区别于面向过程,面向过程是从解决一个问题的过程出发,一步一步的调用函数去解决。而面向对象专注于通过对象的一些方法去解决问题,不同的功能可能由不同的对象来负责解决。
类是一个抽象概念,是一类具有相同特征的事物的统称,比如大学生。
对象是具体的事物,是类的实例,比如具体的大学生张三。
单一职责原则(SRP)
一个对象或方法只做一件事。如果一个方法承担过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。应该把变量或方法划分成较小的颗粒度。
最少知识原则(LKP)
一个软件实体应当尽可能少的与其他实体发生相互作用,应当尽量减少对象之间的交互。如果两个对象之间不必彼此通信,那么这两个对象就不要直接相互联系,可以转交给第三方进行处理。
开放-封闭原则(OCP)
软件实体(类/模块/函数)应该是可以扩展,但是不可修改。是编写一个好程序的目标,其他设计原则都是达到这个目标的过程
当需要改变一个程序的功能或者这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码,防止影响原系统的稳定性。
学习设计模式,有助于写出可复用和维护性高的程序
设计模式的原则是找出程序中变化的地方,并将变化封装起来,他的关键是意图而非结构。
下面列举几个常用的设计模式供大家参考。
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
需求:一些对象我们往往只需要一个,比如说线程池、全局缓存、浏览器中的window对象、登录浮框等。
实现:用一个变量标识当前是否已经为某个类创建过对象,如果是,则在下一次获取这个类的实例时,直接返回之前创建的对象。
// 生成单例
function getSingleton (fn) {
let instance
return function() {
return instance || (instance = fn.apply(this, arguments))
}
}
// 例子--单例元素(登陆框)
function createDiv() {
const div = document.createElement('div')
div.innnerHtml = '登陆框'
document.appendChild(div)
return div
}
const singletonCreateDiv = getSingleton(createDiv)
singletonCreateDiv()
singletonCreateDiv()
// 例子--单例实例
function setHr(name) {
this.hr = name
}
setHr.prototype.getName = function() {
return this.hr
}
const ginsleSetHr = getSingleton(function(name) {
return new setHr(name)
})
ginsleSetHr('xiaoming').getName() // xiaoming
ginsleSetHr('xiaoli').getname() // xiaoming
定义:为一个对象提供一个占位符或者代用品,以便控制对他的访问。
当客户不方便直接访问一个对象或者不满足需求的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理后,再把请求转交给本体对象。代理和本体的接口具有一致性,本体定义了关键功能,而代理是提供或者拒绝对它的访问,或者在访问本体之前做一些额外的事情
优点:对外提供统一的接口,而代理类在接口中实现对真实类的附加操作功能。从而在不影响外部调用的情况下,进行系统的扩展。也就是说,我要修改真实角色的操作的时候,尽量不去修改他,而是在她外部‘包’一层进行附加行为,即代理类。
代理模式主要有三种:保护代理/虚拟代理/缓存代理
// 例子1--节流函数
function throttle(fn, interval) {
let timer
let first = true
return function() {
if (first) {
first = false
fn.apply(this, arguments)
return
}
if (timer) return
timer = setTimeout(() => {
fn.apply(this, arguments)
clearTimeout(timer)
timer = null
}, interval || 500)
}
}
// 例子2--防抖函数
function debounce(fn, interval, immediate) {
let timer
return function() {
// 有定时器就清空定时器
if (timer) clearTimeout(timer)
//重新部署定时器
if (immediate) {
// 立即执行的函数执行时机是在第一次也就是不存在定时器时
if (!timer) fn.apply(this, arguments)
timer = setTimeout(() => {
timer = null
}, interval || 500)
} else {
// 非立即执行的函数执行时机是在最后一次也就是执行完定时器之时
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, interval || 500)
}
}
}
// 例子3--缓存加法
function add() {
return [].reduce.call(arguments, (res, cur) => res += cur, 0)
}
function cacheAdd = (() => {
cache = {}
return function() {
var args = [].slice.call(arguments).sort().join('_')
return cache[args] || (cache[args] = add.apply(this, arguments))
}
})()
// 例子4--虚拟代理:给image加一个loading
function MyImage(src) {
var img = document.createElement('img')
document.body.appendChild(img)
img.src= src
return img
}
function ProxyImage(src) {
var img = new Image
img.src = src
var myImage = new MyImage('./loading.gif')
img.onload = function() {
myImage.src = this.src
}
return myImage
}
ProxyImage('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.2008php.com%2F09_Website_appreciate%2F10-07-11%2F1278861720_g.jpg&refer=http%3A%2F%2Fwww.2008php.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652859748&t=0418ca9b3016e12038e532fb74be952a')
定义:定义一系列算法,把他们一个个封装起来,并且使他们可以相互替换
需求:表单验证、动画(linear/easeIn/strongEaseOut/strongEaseIn)
优点:可以有效地避免多重条件语句,将一系列算法封装起来更直观,利于维护
缺点:往往策略集会比较多,我们需要事先了解定义好所有的情况
将算法的使用和算法的实现分离开来
一个基于策略模式的程序至少由两个部分组成:
// 例子--表单校验
const errorMsgs = {
default: '数据格式有误',
minLength: '数据长度不足',
isNumber: '请输入数字',
required: '内容不可为空'
}
// 策略类
const strategies = {
required(val, errMsg) {
if (val === '') {
return errMsg || errorMsgs['required']
}
},
isNumber(val, errMsg) {
if (!/\d+/.test(val)) {
return errMsg || errorMsgs['isNumber']
}
},
minLength(val, length, errMsg) {
if (val.length < length) {
return errMsg || errorMsgs['minLength']
}
}
}
// 环境类
function Validtor() {
this.cache = {}
}
Validator.prototype.add = function(value, rules) {
const that = this
for (let i = 0, rule; rule = rules[i++];) {
;(function(rule) {
(that.cache[value.name] || this.cache[value.name] = []).push(function() {
const strategyArr = rule.strategy.splite(':')
const strategy = strategyArr.shift()
strategyArr.unshift(value.value)
strategyArr.push(value.message)
return strategies[strategy].apply(this, strategyArr)
})
})(rule)
}
}
Validator.prototype.validate = function() {
for (let key in this.cahce) {
for (i = 0, fn; fn = this.cahce[key][i++];) {
const err = fn()
if (err) {
console.log(err)
break
}
}
}
}
const validator = new Validator()
validator.add({ name: 'username', value: 'asd'}, [{
strategy: 'required',
message: '这个是必填项'
}, {
strategy: 'minLength:8',
message: '名称长度不可少于8位'
}])
validator.validate()
定义:各对象之间的交互操作非常多,每个对象的行为操作都依赖彼此对方,修改一个对象会涉及到很多其他对象的行为。创建一个中介对象,中介者把对象统一管理起来,其他对象通过中介者相互通信。
需求:中介对象主要是来封装行为的,行为的参与者就是各个对象,但是通过中介者,这些对象不用相互知道。聊天室/泡泡糖游戏等都是中介者模式等实例
优点:降低对象之间的耦合性,使得对象易于独立的被复用。提高系统灵活性,使得系统易于扩展和维护。中介者模式使网状的多对多关系变成了 相对简单的一对多关系。高内聚,低耦合,使用中介者明显降低了对象之间的耦合
// 例子--聊天室
// 参与者类
class Participant {
constructor(name) {
this.name = name
this.chatRoom = null
}
send(message, person) {
this.chatRoom.send(message, this, person)
}
receive(message, person) {
console.log(`${person.name}发来消息:${message}`)
}
}
// 聊天室类
class ChatRoom {
constrctor() {
this.participants = {}
}
register(person) {
if (this.participants[person.name]) return
this.participants[person.name] = person
person.chatRoom = this
}
send(message, from, to) {
if (to) {
if (!this.participants[to.name]) return
to.receive(message, from)
} else {
for (let p in this.participants) {
this.participants[p].receive(message, from)
}
}
}
}
const xiaoming = new Participant('xiaoming')
const xiaoli = new Participant('xiaoli')
const afang = new Participant('afang')
const chatRoom = new ChatRoom()
chatRoom.register(xiaoming)
chatRoom.register(xiaoli)
chatRoom.register(afang)
xiaoming.send('我爱你 你会接受我吗?')
xiaoli.send('不,你不理解我')
afang.send('私事私聊好吗', xiaoming)
xiaoming.send('不好意思 发错地方了')
afang.send('小明人不错的 你可以考虑一下哦 哈哈', xiaoli)
定义:也称作发布订阅模式,定义了对象之间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
需求:DOM 事件监听也是 “发布订阅模式” 的应用
优点:事件上的解耦,对象之间的解耦
function Observer() {
this.cache = {}
}
Observer.prototype.subscribe = function(type, fn) {
(this.cache[type] || (this.cache[type] = [])).push(fn)
}
Observer.prototype.unsubscribe = function(type, fn) {
if (!this.cache[type]) throw('无注册该方法')
if (fn) {
const fnIdx = this.cache[type].indexOf(fn)
if (fnIdx === -1) throw('无订阅该函数')
this.cache[type].splice(fnIdx, 1)
} else {
this.cache[type] = []
}
}
Observer.prototype.publish = function(...args) {
const [type, ...arg] = args
if (!this.cache[type]) throw('无注册该方法')
for (let i = 0, fn; fn = this.cache[type][i++]) {
fn.apply(this, arg)
}
}
const observer = new Observer()
observer.subscribe('click', function() {
console.log('click fn1')
})
function clickFn2() {
console.log('clickFn2', arguments)
}
observer.subscribe('click', clickFn2)
observer.publish('click')
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到一个对象处理它为止。请求发送者只需要知道链中的第一个节点,从而弱化了发送者和接受者之间的强联系。
需求:挤公交—车上人多,只需要把钱传递给前面的人,如果不是售票员就一直传递下去,直到售票员。
考试抄袭—遇到不会的题,就把题目编号写在纸条上往后传递,直到有人会解这道题。
请假流程— 小于两天经理直接审批,大于两天要经理上级审批。等
// Chain类
class Chain {
constructor(fn) {
this.fn = fn
this.nextChain = null
}
setNext(chain) {
this.nextChain = chain
return chain
}
passRequest() {
const value = this.fn.apply(this, arguments)
if (value === 'pass') {
return this.nextChain && this.nextChain.passRequest.apply(this.nextChain, arguments)
}
return value
}
next() {
return this.nextChain && this.nextChain.passRequest.apply(this.nextChain, arguments)
}
}
// 例子--公司OA申请
// 申请:(请假类型, 数字)
// type: 1:请假;2:调薪
// number: 天数/金额
// 请假类型,如果天数小于2天可有LevelA登记的领导直接审批
// 请假类型,如果天数大于2天小于5天,审批流是levelA -> levelB
// 请假类型,如果天数大于5天,审批流是levelA -> levelB -> levelC
// 调薪类型,审批流是levelA -> levelB -> levelC
// levelA:
const levelA = new Chain(function(type, num) {
if (type === '1' && num <= 2) {
console.log('请假',num, '天通过')
return true
} else {
return 'pass'
}
})
// levelB:
const levelB = new Chain(function(type, num) {
if (type === '1' && num <= 5) {
console.log('请假',num, '天通过')
return true
} else {
return 'pass'
}
})
// levelC:
const levelC = new Chain(function(type, num) {
if (type === '1') {
console.log('请假',num, '天通过')
return true
} else {
if (num <= 500) {
console.log('调薪', num, '元通过')
} else {
console.log('调薪', num, '元待定')
}
}
})
levelA.setNext(levelB).setNext(levelC)
levelA.passRequest(1, 4)
定义:允许对象在其内部状态改变时改变它的行为,对象看起来像修改了它的类
优点:
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
- 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支
- Context中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。
需求:灯开关、文件上传、状态机
// 例子--开关灯
// class OffStatus {
// constructor(target) {
// this.light = target
// }
// handler() {
// console.log('打开灯')
// this.light.btn.innerHTML = '关灯'
// this.light.setStatus(this.light.onStatus)
// }
// }
// class OnStatus {
// constructor(target) {
// this.light = target
// }
// handler() {
// console.log('关闭灯')
// this.light.btn.innerHTML = '开灯'
// this.light.setStatus(this.light.offStatus)
// }
// }
// 状态类工厂函数;各个状态类出了handler处理方式不同其他都一样,所以这里可用抽象工厂模式产生状态类
function StatusFactory(props) {
function State() {}
State.prototype.handler = function() {
throw('子类要重写该方法')
}
function Fn(target) {
this.light = target
}
Fn.prototype = new State()
for (let k in props) {
Fn.prototype[k] = props[k]
}
return Fn
}
const OffStatus = StatusFactory({
handler() {
console.log('打开灯')
this.light.btn.innerHTML = '关灯'
this.light.setStatus(this.light.onStatus)
}
})
const OnStatus = StatusFactory({
handler() {
console.log('关闭灯')
this.light.btn.innerHTML = '开灯'
this.light.setStatus(this.light.offStatus)
}
})
class Light {
constructor() {
this.btn = null
this.offStatus = new OffStatus(this)
this.onStatus = new OnStatus(this)
this.status = this.offStatus
this.init()
}
init() {
const btn = document.createElement('button')
btn.innerHTML = '开灯'
this.btn = btn
document.body.appendChild(btn)
btn.addEventListener('click', () => {
this.status.handler()
})
}
setStatus(status) {
this.status = status
}
}
const light = new Light()
定义:享元(flyweight)模式是一种用于性能优化的模式,它的目标是尽量减少共享对象的数量
运用共享技术来有效支持大量细粒度的对象。
强调将对象的属性划分为内部状态(属性)与外部状态(属性)。内部状态用于对象的共享,通常不变;而外部状态则剥离开来,由具体的场景决定。
需求:在程序中使用了大量的相似对象时,可以利用享元模式来优化,减少对象的数量。
// 例子--测量学生身体素质
// n个学生,无需创建n个对象,只需以性别为内部属性创建两个对象即可。
class Fitness {
constructor(sex) {
this.sex = sex
}
judge(name) {
fitnessManager.updateFitnessData(name, this)
var res = name + ': '
if (this.sex === 'male') {
res += this.judgeMale()
} else {
res += this.judgeFemale()
}
}
judgeMale() {
var radio = this.height / this.weight
return this.age > 20 ? (radio > 3.5) : (radio > 2.8)
}
judgeFemale() {
var radio = this.height / this.weight
return this.age > 20 ? (radio > 4) : (radip > 3)
}
}
// 工厂模式创建对象
var fitnessFactory = {
objs: {},
create(sex) {
return this.objs[sex] || (this.objs[sex] = new Fitness(sex))
}
}
// 外部属性操作
const fitnessManager = {
// 储存数据
database = {},
add(name, sex, age, height, weight) {
const fitness = fitnessFactory.create(sex)
this.database[name] = { age, height, weight}
return fitness
},
// 把储存的数据,更新到当前正在使用的对象
updataFitnessData(name, obj) {
const data = this.database[name]
for (let p in data) {
if (data.hasOwnProperty(p)) {
obj[p] = data[p]
}
}
}
}
var a = fitnessManager.add('A', 'male', 18, 160, 80)
var b = fitnessManager.add('B', 'male', 21, 180, 70)
var c = fitnessManager.add('B', 'male', 18, 170, 60)
var d = fitnessManager.add('C', 'female', 18, 160, 40)
a.judge('A')
b.judge('B')
c.judge('C')
d.judge('d')
// 例子--微云文件上传
function Upload(type) {
this.type = type
}
Upload.prototype.delFile = function(id) {
uploadManager.mergeExternalState(id, this)
if (this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom)
}
if (window.confirm('确定删除该文件吗?' + this.fileName)) {
return this.dom.parentNode.removeChild(this.dom)
}
}
const UploadFactory = (function() {
const cache = {}
return {
create(type) {
return cache[type] || (cache[type] = new Upload(type))
}
}
})()
const uploadManager = {
database: {},
add(id, type, fileName, fileSize) {
const uploader = UploadFactory.create(type)
const dom = document.createElement('div')
dom.innerHTML = `文件名称:${fileName}, 文件大小:${fileSize}`
dom.querySelector('.delFile').onclick = function() {
uploader.delFile(id)
}
document.body.appendChild(dom)
return uploader
},
mergeExternalState(id, target) {
const data = this.database[id]
for (let p in data) {
target[i] = data[i]
}
}
}
let id = 0
const startUpload = (type, files) => {
for (let i = 0, file; file = files[i++];) {
uploadManager.add(id++, type, file.fileName, file.fileSize)
}
}
startUpload('plugin', [{
fileName: '1.txt',
fileSize: 10000
}, {
fileName: '2.zip',
fileSize: 3000000
}])
startUpload('flash', [{
fileName: '1.txt',
fileSize: 10000
}, {
fileName: '2.zip',
fileSize: 3000000
}])
定义:以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
是一种“即用即付”的方式,能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责
需求:扩展一个类的功能或者给一个类添加附加职责。给一个对象动态的添加功能,或动态撤销功能
优点:
- 继承的有力补充,比继承灵活,不改变原对象的情况下给一个对象扩展功能。(继承在扩展功能是静态的,必须在编译时就确定好,而使用装饰者可以在运行时决定,装饰者也建立在继承的基础之上)
- 通过使用不同装饰类以及这些类的排列组合,可以实现不同的效果
- 符合开闭原则
Function.prototype.before = function(fn) {
const self = this
return function() {
fn.apply(this, arguments)
return self.apply(this, arguments)
}
}
Function.prototype.after = function(fn) {
const self = this
return function() {
const res = self.apply(this, arguments)
fn.apply(this, arguments)
return res
}
}
// 例子--ajax加token解决CSRF攻击
var ajax = function(type, url, params) {
console.dir(params)
}
// ajax('type', 'http://xx.com/userinfo', {name:'match'})
var getToken = function() {
return 'Token'
}
ajax.before(getToken).('type', 'http://xx.com/userinfo', {name:'match'})
// 例子--表单提交有两个操作表单验证和ajax提交
var username = document.getElementById( 'username' ),
password = document.getElementById( 'password' ),
submitBtn = document.getElementById( 'submitBtn' );
var formSubmit = function(){
if ( username.value === '' ){
return alert ( '用户名不能为空' );
}
if ( password.value === '' ){
return alert ( '密码不能为空' );
}
var param = {
username: username.value,
password: password.value
}
ajax( 'http://xx.com/login', param ); // ajax 具体实现略
}
submitBtn.onclick = function(){
formSubmit();
}
// 优化后
Function.prototype.before = function(beforeFn) {
var self = this
return function() {
if (beforeFn.apply(this, arguments) === false) {
return
}
return self.apply(this, arguments)
}
}
var validata = function(){
if ( username.value === '' ){
alert ( '用户名不能为空' );
return false;
}
if ( password.value === '' ){
alert ( '密码不能为空' );
return false;
}
}
var formSubmit = function(){
var param = {
username: username.value,
password: password.value
}
ajax( 'http://xx.com/login', param );
}
formSubmit = formSubmit.before(validata)
submitBtn.onclick = function(){
formSubmit();
}
定义:将其成员对象的实例化推迟到子类来实现的类。
需求:创建对象的流程赋值的时候,比如依赖于很多设置文件等;处理大量具有相同属性的小对象
优点:不暴露创建对象的具体逻辑,而是将逻辑封装在函数中。
分类:简单工厂,工厂方法,抽象工厂(简单工厂和工厂方法都是生成实例,而抽象工厂是生成类)
// 工厂方法
function Factory(type, content) {
return this instanceof Factory ? new Factory[type](content) : new Factory(type, content)
}
function introduce() {
console.log(`i am a ${this.role}`. my name is ${this.feature.name})
}
Factory.prototype = {
Student: function Student(feature) {
this.role = 'student'
this.feature = feature
Student.prototype.introduce = introduce
},
Teacher: funtion Teacher(feature) {
this.role = 'teacher'
this.feature = feature
Teacher.prototype.introduce = introduce
}
}
const xiaoming = new Factory('Student', { name: 'xiaoming', sex: 'male' , age: 10 })
const shaoalong = new Factory('Teacher', { name: 'shaoalone', sex: 'male' , age: 29 })
console.log(xiaoming)
console.log(shaoalong)
shaoalong.introduce()
// 抽象工厂--所谓抽象工厂就是类继承的实现
const classMap = {
WechatUser: function WechatUser() {
this.type = 'wechat'
WechatUser.prototype = {
getName() {
return new Error('抽象方法不能调用');
}
}
},
QqUser: function QqUser() {
this.type = 'qq'
WechatUser.prototype = {
getName() {
return new Error('抽象方法不能调用');
}
}
},
WeiboUser: function QqUser() {
this.type = 'weibo'
WechatUser.prototype = {
getName() {
return new Error('抽象方法不能调用');
}
}
}
}
// 类继承实现
function AccountAbstractFactory(curClass, superType) {
if (typeof classMap[superType] === 'function') {
function fn() {}
fn.prototype = new classMap[superType]()
curClass.prototype = new fn()
curClass.constructor = curClass
} else {
throw new Error('抽象类不存在!')
}
}
function UserOfWechat(name) {
this.name = name
this.viewPage = ['首页','通信录']
}
AccountAbstractFactory(UserOfWechat, 'WechatUser')
UserOfWechat.prototype.getName = function() {
return this.name;
}
function UserOfQq(name) {
this.name = name
this.viewPage = ['首页', '通讯录', '发现页']
}
AccountAbstractFactory(UserOfQq, 'QqUser')
UserOfWechat.prototype.getName = function() {
return this.name
}
const xiaoming = new UserOfWechat('xiaoming')
const xiaoli = new UserOfQq('xiaoli')