- Published on
JavaScript设计模式(上篇)
- Authors
- Name
- Hansuku
JavaScript 设计模式
- 解析 ES6 所需要的几个 babel 插件
npm install babel-core babel-loader babel-polyfill babel-preset-es2015
面向对象三要素
继承
class Person{
constructor(name,age){
this.name = name
this.age = age
}
eat(){
console.log(`${this.name} eat something`)
}
speak(){
console.log(`My name is ${this.name}, age ${this.age}`)
}
}
//继承
class Student extends Person{
constructor(name,age,number){
super(name,age)
this.number = number
}
study(){
console.log(`${this.number}号${this.name}同学正在学习`)
}
}
let han = new Student('han',20,20)
han.eat()
han.speak()
han.study()
封装
//ts运行环境
class People {
name
age
protected weight
constructor(name, age) {
this.name = name
this.age = age
this.weight = 120
}
eat(){
console.log(`${this.name} eat something`)
}
speak(){
console.log(`My name is ${this.name}, age ${this.age}`)
}
}
//继承
class Student extends People{
number
private girlfriend
constructor(name,age,number){
super(name,age)
this.number = number
this.girlfriend = 'xiaoli'
}
study(){
console.log(`${this.number}号${this.name}同学正在学习`)
}
getWeight() {
console.log(`weight ${this.weight}`)
}
}
let xiaoming = new Student('xiaoming', 20, 'A1')
xiaoming.getWeight()
console.log(xiaoming.girlfriend)//这里会报错 girlfriend是私有的
console.log(xiaoming.weight)//这里会报错 weight只能由类本身和子类来读取
多态
class People{
constructor(name){
this.name = name
}
}
class A extends People{
constructor(name){
super(name)
this.name = name
}
saySomething(){
alert('I am A')
}
}
class B extends People{
constructor(name){
super(name)
this.name = name
}
saySomething(){
alert('I am B')
}
}
let a = new A()
a.saySomething()//I am A
let b = new B()
b.saySomething()//I am B
ES6 模拟 jQuery
class jQuery{
constructor(seletor){
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(seletor))//遍历dom获取seletor节点
let len = dom ? dom.length : 0
for(let i = 0;i < len;i++){
this[i] = dom[i]
}
this.length = len
this.seletor = seletor || ''
}
append(node){
}
addClass(name){
}
html(node){
}
//此处省略N个API
}
window.$ = function(seletor){
return new jQuery(seletor)
}
var $p = $('p')
console.log($p)
console.log($p.addClass)
UML 类图
属性、方法前的+#-代表着公共、继承和私有
设计原则
设计模式=设计+模式 设计=设计原则
何谓设计
- 即按照哪一种四路或者标准来实现业务功能
- 功能相同,可以有不同的设计方案来实现
- 伴随需求增加,设计的作用才能体现出来
设计准则
- 准则 1:小即是美
- 准则 2:让每个程序只做好一件事
- 准则 3:快速建立原型
- 准则 4:舍弃高效率而取可移植性
- 准则 5:采用纯文本来存储数据
- 准则 6:充分利用软件的杠杆效应(重复利用)
- 准则 7:使用 shell 脚本来提高杠杆效应的可移植性
- 准则 8:避免强制性的用户界面
- 准则 9:让每个程序都成为过滤器
- 小准则:允许用户定制环境
- 小准则:尽量使操作系统内核小而轻量化
- 小准则:使用小写字母并尽量简短
- 小准则:沉默是金
- 小准则:各部分之和大于整体
- 小准则:寻求 90%的解决方案(二八定律)
SOLID 五大设计原则
- S 单一职责原则(single)
- O 开放封闭原则(open-close)
- L 李氏置换原则(Liskov Substitution Principle: LSP)
- I 接口独立原则(interface)
- D 依赖导致原则(Dependence Inversion Principle)
用 Promise 来说明 SO
function loadImg(src){
let promise = new Promise((resolve, reject) => {
let img = document.createElement('img')
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject('图片加载失败')
}
img.src = src
});
return promise
}
let src = 'https://www.hansuku.com/wp-content/themes/Lover/images/thumbs/5.jpg'
let result = loadImg(src)
result.then(img=>{
//part1
alert(`width:${img.width}`)
return img
}).then(img=>{
//part2
alert(`height:${img.height}`)
}).catch(ex=>{
alert(ex)
})
//每增加一个需求就加一个then,一个then只做一件事
从设计到模式
创建型
- 工厂模式(工厂方法模式,抽象工厂模式,建造者模式)
- 单例模式
- 原型模式
结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
行为型
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代器模式
- 职责连模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
拟真题 1
打车时,可以打专车或者快车,任何车都有车牌号和名称 不同车价格不同,快车每公里 1 元,专车每公里 2 元 行程开始时,显示车辆信息 行程结束时,显示打车金额(假定行程为 5 公里)
class Car{
constructor(name,car_num){
this.name = name
this.car_num = car_num
}
}
class special_car extends Car{
constructor(name,car_num){
super(name,car_num)
this.km_price = 2
}
}
class express_car extends Car{
constructor(name,car_num){
super(name,car_num)
this.km_price = 1
}
}
class trip{
constructor(car){
this.car = car
}
start(){
console.log(`行程开始,车型${this.car.name},车牌号${this.car.car_num}`)
}
end(){
console.log('行程结束,价格:'+(this.car.km_price * 5))
}
}
let car = new special_car('五菱宏光','浙A10086')
let Trip = new trip(car)
Trip.start()
Trip.end()
拟真题 2
某停车场 分 3 层 每层 100 车位 每个车位都能监控都车辆的驶入和离开 车辆进入前,显示每层空余车位数量 车辆进入时,摄像头可识别车牌号和时间 车辆离开时,出口显示器显示车牌号和停车时常
class Car{
constructor(num){
this.num = num
}
}
class ParkLot{
constructor(floors){
this.floors = floors || []
this.carList = {} //存储拍摄返回的信息
this.camera = new Camera()
this.screen = new Screen()
}
emptyNum(){
return this.floors.map(floor=>{
return `${floor.index} 层还有 ${floor.emptyPlaceNum()} 个空闲车位`
}).join('\n')
}
in(car){
//通过摄像头获取信息
const info = this.camera.shot(car)
//听到某个停车场
const i = parseInt(Math.random() * 100 % 100)
const place = this.floors[0].places[i]
place.in()
info.place = place
//记录信息
this.carList[car.num] = info
}
out(car){
//获取信息
const info = this.carList[car.num]
const place = info.place
place.out()
//显示时间
this.screen.show(car,info.inTime)
delete this.carList[car.num]
}
}
class Camera{
constructor(car){
this.car = car
}
shot(car){
return{
num: car.num,
inTime: Date.now()
}
}
}
class Screen{
show(car,inTime){
console.log('车牌号',car.num)
console.log('停车时间',Date.now() - inTime)
}
}
class Floor{
constructor(index,places){
this.index = index
this.places = places || []
}
emptyPlaceNum(){
let num = 0
this.places.forEach(p=>{
if(p.empty){
num = num+1
}
})
return num
}
}
class Place{
constructor(){
this.empty = true
}
in(){
this.empty = false
}
out(){
this.empty = true
}
}
//初始化停车场
const floors = []
for(let i = 0;i < 3;i++){
const places = []
for(let j = 0;j < 100;j++){
places[j] = new Place()
}
floors[i] = new Floor(i+1,places)
}
const park = new ParkLot(floors)
//初始化车辆
const car1 = new Car(100)
const car2 = new Car(200)
const car3 = new Car(300)
console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第二辆车离开')
park.out(car2)
console.log('第三辆车离开')
park.out(car3)
console.log(park.emptyNum())
工厂模式
工程模式意在把所有类都封装到一个创建者上,通过创建者调用类 类似jQuery:
window.$ = function(seletor){return new jQuery(seletor)}
这里就是构造者,使用者不需要new jQuery("p")
而可以直接调用$('p')
class Product{
constructor(name){
this.name = name
}
init(){
alert('init')
}
fn1(){
alert('fn1')
}
fn2(){
alert('fn2')
}
}
class Creator{
create(name){
return new Product(name)
}
}
let creator = new Creator()
let p = creator.create('p1')
p.init()
p.fn1()
单例模式
单例模式意在每个 class 只能 new 一次 比如下面的代码 obj1 和 obj2 虽然都 new 了 SingleObject 但是他们两个是完全相等的 而简单的 JS 不能够实现类似 java 那样完全拒绝重复 new 下面的代码中,obj3 是一个 new 出来的 SingleObject,虽然不会报错,但是这个时候会发现 obj1 != obj3
class SingleObject{
login(){
console.log('login')
}
}
SingleObject.getInstance = (function(){
let instance
return function(){
if(!instance){
instance = new SingleObject()
}
return instance
}
})()
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2)
let obj3 = new SingleObject() //无法完全控制
obj3.login()
console.log(obj1 === obj3) //false
类似的场景就像我们在 jQuery 中声明了多次**$**或者是错误的引用了 jQuery 好几遍,但是我们使用的时候都只是在使用那一个,内部会有一个处理,如果检测到了当前挂载的内存中有 window.jQuery 那么就直接使用当前内存的 window.jQuery,否则新生成一个 这样就能保证 jQuery 在当前页面生命周期里永远只有一个实例 类似的还有 vuex 和 redux 里的 store
// jQuery只有一个`$`
if(window.jQuery != null){
return window.jQuery
}else{
// do init...
}
class LoginForm{
constructor(){
this.state = 'hide'
}
show(){
if(this.state === 'show'){
alert('已经显示')
return
}
this.state = 'show'
console.log('now show')
}
hide(){
if(this.state === 'hide'){
alert('已经隐藏')
return
}
this.state = 'hide'
console.log('now hide')
}
}
LoginForm.getInstance = (function(){
let instance
return function(){
if(!instance){
instance = new LoginForm()
}
return instance
}
})()
let login1 = LoginForm.getInstance()
login1.show() //正常执行
let login2 = LoginForm.getInstance()
login2.show() //alert('已经显示')
适配器模式
上图中,Adaptee 是需要被适配、转换的类 Target 是转换器,需要使用 Adaptee,然后
request()
暴露给外部
class Adaptee{
specificRequest(){
return '德国标准插头'
}
}
class Target{
constructor(){
this.adaptee = new Adaptee()
}
request(){
let info = this.adaptee.specificRequest()
return `${info} - 转换器 - 中国标准插头`
}
}
let target = new Target()
let res = target.request()
console.log(res) //德国标准插头 - 转换器 - 中国标准插头
类似的 如果我们以前在项目里大量的使用$.ajax 当我们要替换掉 jQuery,就得需要一个适配去适配它
//新接口
ajax({
url:'/123',
type:'post',
dataType:'json',
data:{
id:"123"
}
})
.done(function(){})
//老接口
$.ajax({
url: '/123',
type: 'post',
data: {
id:"123"
},
dataType: 'json',
success: function(res) {
},
error: function() {
}
});
// 做一层适配器
var $ = {
ajax:function(options){
return ajax(options)
}
}
//完成兼容
装饰器模式
class Circle{
draw(){
console.log('画一个圆形')
}
}
class Decorator{
constructor(circle){
this.circle = circle
}
draw(){
this.circle.draw()
this.setRedBorder(circle)
}
setRedBorder(circle){
console.log('设置红色边框')
}
}
//test
let circle = new Circle()
circle.draw()
let dec = new Decorator(circle)
dec.draw()
ES7 中拥有装饰器的功能,而且有一些特别好的库已经封装了装饰器(core-decorators) ES7 配置环境 安装兼容
npm install babel-plugin-transform-decorators-legacy --save-dev
.babelrc{ "presets":["es2015","latest"], "plugins": ["transform-decorators-legacy"] }
然后测试 ES7 环境
@testDec class Demo{ } function testDec(target){ target.isDec = true } console.log(Demo.isDec)
@decorator class A {} //等同于 class A {} A = decorator(A) || A;
只读装饰器
function readonly(target,name,descriptor){
descriptor.writable = false
return descriptor
}
class Person{
constructor(){
this.first = 'A'
this.last = 'B'
}
@readonly
name(){
return `${this.first} ${this.last}`
}
}
let p = new Person()
console.log(p.name())
// p.name = function(){ //这里会报错,name是只读的
// alert(100)
// }
function log(target,name,descriptor){
let oldValue = descriptor.value
descriptor.value = function(){
console.log(`calling ${name} with`,arguments)
return oldValue.apply(this, arguments)
}
return descriptor
}
class Math{
@log
add(a,b){
return a + b
}
}
let math = new Math()
const result = math.add(2,4)
console.log(result)//会在返回相加值前打印arguments
使用插件预设的装饰器
//安装 npm install core-decorators --save
import { readonly } from 'core-decorators'
class Person{
@readonly
name(){
return 'zhang san'
}
}
let p = new Person()
alert(p.name())
p.name = function(){} //只读报错
import { deprecate } from 'core-decorators'
class Person{
@deprecate('该接口即将废除')
name(){
return 'zhang san'
}
}
let p = new Person()
console.log(p.name())//提示该接口即将废除
代理模式
class RealImg{
constructor(filename){
this.filename = filename
this.loadFromDisk() //从硬盘中加载 模拟
}
display(){
console.log('display'+this.filename)
}
loadFromDisk(){
console.log('loading...'+this.filename)
}
}
class ProxyImg{
constructor(filename){
this.realImg = new RealImg(filename)
}
display(){
this.realImg.display()
}
}
let proimg = new ProxyImg('1.png')
proimg.display()
ES6 proxy
//明星
let star = {
name: '马天宇',
age: 25,
phone: '1351212121',
}
let agent = new Proxy(star,{
get:function(target,key){
if(key === 'phone'){
//返回经纪人电话
return '1357777777'
}
if(key === 'price'){
//明星不报价 经纪人报价
return 120000
}
return target[key]
},
set:function(target,key,val){
if(key === 'customerPirce'){
if(val < 100000){
//最低 10万
throw new Error('价格太低')
}else{
target[key] == value
return true
}
}
}
})
//test
console.log(agent.name)//马天宇
console.log(agent.age)//25
console.log(agent.phone)//1357777777
console.log(agent.price)//120000
agent.customerPirce = 90000//报错 价格太低
外观模式
外观模式意在为底层接口做一个高层接口,比如去一个医院看病,有人帮你把挂号、取号、排队等等操作都给你做了 而具体可以在定义函数传参的时候 如果你想省缺某个中间的函数 即在内部做一个转换 对外部不影响
function bindEvent(elem, type, selector, fn){
if(fn == null){
fn = selector
selector = null
}
//coding
}
//use
bindEvent(elem, 'click', '#div', fn)
bindEvent(elem, 'click', fn)
谨慎:外观模式不符合单一职责原则和开放封闭原则 不可滥用
观察者模式
//主题,保存状态,状态发生之后触发所有观察者对象
class Subject{
constructor(){
this.state = 0
this.observers = []
}
getState(){
return this.state
}
setState(state){
this.state = state
this.notifyAllObservers()
}
notifyAllObservers(){
this.observers.forEach(observer => {
observer.update()
})
}
attach(observer){
this.observers.push(observer)
}
}
//观察者
class Observer{
constructor(name,subject){
this.subject = subject
this.name = name
this.subject.attach(this)
}
update(){
console.log(`${this.name} update, state: ${this.subject.getState()}`)
}
}
//test
let s = new Subject()
let o1 = new Observer('o1',s)
let o2 = new Observer('o2',s)
let o3 = new Observer('o3',s)
s.setState(1)
s.setState(2)
s.setState(3)
网页事件绑定
<button id ="btn1">btn</btn>
<script>
$('#btn1').click(function(){
console.log(1)
})
$('#btn1').click(function(){
console.log(2)
})
$('#btn1').click(function(){
console.log(3)
})
</script>
所有 UI 界面的时间监听机制都是观察者模式
Promise
var src = 'https://image.biadu.com/1.png'
var result = loadImg(src)
result.then(function(img){
console.log('width',img.width)
return img
}).then(function(img){
console.log('height',img.height)
})
NodeJs 自定义事件
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
//监听 some 事件
emitter1.on('some',info=>{
console.log('fn1', info)
})
//监听 some 事件
emitter1.on('some',info=>{
console.log('fn2', info)
})
//触发 some 事件
emitter1.emit('some','xxxx')
// 继承
const EventEmitter = require('events').EventEmitter
class Doc extends EventEmitter{
constructor(name){
super()
this.name = name
}
}
let simon = new Dog('simon')
simon.on('brak',function(){
console.log(this.name, 'barked_1')
})
simon.on('brak',function(){
console.log(this.name, 'barked_2')
})
setInterval(function(){
simon.emit('bark')
},1000)
//stream 用到自定义事件 fs分段加载文件
const fs = require('fs')
const readStream = fs.createReadStream('./a_big_file')
let length = 0
readStream.on('data',function(chunk){
let len = chunk.toString().length
length += len
console.log(len)
})
readStream.on('end',function(){
console.log('length',length)
})
//stream 用到自定义时间 fs分行加载文件
const fs = require('fs')
const readline = require('readline')
let rl = readline.createInterface({
input: fs.createReadStream('./a_big_file')
})
let lineNum = 0
rl.on('line',function(line){
lineNum ++
})
rl.on('close',function(){
console.log(lineNum)
})
更多的情况是自定义回调,扩展多进程通讯 Vue 和 React 组件生命周期的触发也是依靠观察者模式,当然 vue watch 也是