javascript设计模式
1. 面向对象设计SOLID五大原则
(参考:https://www.cnblogs.com/zhutianpeng/p/3456776.html)
1.S(single):单一职责原则
一个类或者程序有且只有一个职责。 如果功能过于复杂,就拆分开,每个部分保持独立
2.O(open)开放封闭原则
软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。
增加需求时,扩展新代码,而非区修改已有代码
3.L:李氏置换原则(js中使用较少)
子类型必须能够替换它们基类型,子类能覆盖父类
4.I(interface)接口独立原则(js中使用较少)
接口应该仅包含必要的方法,而不该包含其它的。避免出现“胖接口”(类似于单一职责,但是这里更关注接口)
比如:
注意到IBird接口包含很多鸟类的行为,包括Fly()行为.现在如果一个Bird类(如Ostrich鸵鸟)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich不会飞). 这个”胖接口”应该拆分未两个不同的接口,IBird和IFlyingBird,IFlyingBird继承自IBird. 这里如果一种鸟不会飞(如Ostrich),那它实现IBird接口。如果一种鸟会飞(如KingFisher),那么它实现IFlyingBird
5.D(dependency)依赖导致原则(js中使用较少)
面向接口编程,依赖于抽象不依赖于具体,
使用方只关注接口,而不是具体类的实现。
2.简单工厂模式
将new单独封装
遇到new时,就要考虑是否该考虑使用工厂模式
示例:你要吃汉堡,需要new 汉堡,汉堡店将new 汉堡的过程封装了,我们直接到汉堡店中去获取汉堡即可,而不用直接new 汉堡,但是要借助 ”汉堡店“这个对象。
class Product {
constructor(name){
this.name=name;
}
init(){
console.log("init")
}
}
class Creator{
create(name){
return new Product(name)
}
}
测试如下:
在前端中遇到的场景:
jQuery中的$
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
append(node) {
}
addClass(name) {
}
html(data) {
}
// 此处省略若干 API
}
window.$ = function (selector) {
return new jQuery(selector)
}
React.reateElement
Vue异步组件
3.单例模式
一个类只有一个实例
系统中被唯一使用
示例:
登录框
购物车
代码:
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)
前端使用场景:
jQuery只有一个$
if(window.jQuery!==null){
return window.jQuery
}else{
//初始化
}
模拟登录框:
class LoginForm {
constructor() {
this.state = 'hide'
}
show() {
if (this.state === 'show') {
alert('已经显示')
return
}
this.state = 'show'
console.log('登录框已显示')
}
hide() {
if (this.state === 'hide') {
alert('已经隐藏')
return
}
this.state = 'hide'
console.log('登录框已隐藏')
}
}
LoginForm.getInstance = (function () {
let instance
return function () {
if (!instance) {
instance = new LoginForm();
}
return instance
}
})()
// 一个页面中调用登录框
let login1 = LoginForm.getInstance()
login1.show()
// login1.hide()
// 另一个页面中调用登录框
let login2 = LoginForm.getInstance()
login2.show()
// 两者是否相等
console.log('login1 === login2', login1 === login2)
vuex和redux中的store
4.代理模式
代码:
class Adaptee{
specificRequest(){
return "外国插头"
}
}
class Target{
constructor(){
this.adaptee=new Adaptee()
}
request(){
let info=this.adaptee.specificRequest()
return info+'-->转化器-->中国标准的插头'
}
}
测试:
前端中常见使用场景
封装旧接口
//封装自己的ajax,使用方式如下。
ajax({
url:'/getData',
type:"Post",
dataType:'json',
data:{
id:"123"
}
})
.done(function(){})
假设:由于理食原因,代码中的使用方式要使用
$.ajax({....})
那么我们需要再次封装上边的ajax
var $={
ajax:function(options){
return this.ajax(option);
}
}
vue的computed的作用
一般在vue中,要对传入的参数进行特定改造,就可以使用computed
5.装饰器模式
作用
为对象添加新功能
不改变其原有的结构和功能
代码:
class Circle{
draw(){
console.log("画圆")
}
}
//我们想实现一个功能,是画圆,同时打印一句话
class Decorator{
constructor(circle){
this.circle=circle
}
draw(){
this.circle.draw()
console.log('画完圆后,我还要做很多其他功能')
}
}
测试
前端常见使用场景
1,ES7装饰器
2.core-decorators(第三方开源lib)
提供常用的装饰器
// import { readonly } from 'core-decorators'
// class Person {
// @readonly
// name() {
// return 'zhang'
// }
// }
// let p = new Person()
// alert(p.name())
// // p.name = function () { /*...*/ } // 此处会报错
import { deprecate } from 'core-decorators';
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
facepalmHarder() {}
}
let person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
6.代理模式
特点:
使用者无权访问目标对象
中间加代理,通过代理做授权和控制
示例
科学上网
明星经纪人
代码:
class ReadImg{
constructor(fileName){
this.fileName=fileName;
this.loadFromDist()//初始化,从硬盘中加载,模拟
}
display(){
console.log("展示。。。"+this.fileName)
}
loadFromDist(){
console.log("从硬盘加载"+this.fileName)
}
}
//输入参数是一样的。都是fileName,
class ProxyImg{
constructor(fileName){
this.realImg=new ReadImg(fileName)
}
display(){
this.realImg.display()
}
}
前端常见使用场景
网页事件代理(addEventListener)
jQuery $.proxy
ES6 Proxy
// 明星
let star = {
name: '张XX',
age: 25,
phone: '13910733521'
}
// 经纪人
let agent = new Proxy(star, {
get: function (target, key) {
if (key === 'phone') {
// 返回经纪人自己的手机号
return '18611112222'
}
if (key === 'price') {
// 明星不报价,经纪人报价
return 120000
}
return target[key]
},
set: function (target, key, val) {
if (key === 'customPrice') {
if (val < 100000) {
// 最低 10w
throw new Error('价格太低')
} else {
target[key] = val
return true
}
}
}
})
// 主办方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)
// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 150000
// agent.customPrice = 90000 // 报错:价格太低
console.log('customPrice', agent.customPrice)
代理模式vs适配器模式
适配器模式:提供一个不同的接口(如不同版本的插头)
代理模式:提供一模一样的接口
代理模式vs装饰模式
装饰器模式:扩展功能,原有功能不变,可以直接使用
代理模式:显示原有功能,但是经过限制或者阉割之后。
7.外观模式
特点:
为子系统中第一组接口提供一个高层接口
使用者使用这个高层接口
使用场景
函数参数的变化
function bindEvent(elem,type,selector,fn){
if(fn===null){
fn=selector
selector=null
}
}
//调用
bindEvent(elem,"click","#div1",fn)
bindEvent(elem,"click",fn)
8.观察者模式(!!!重点)
特点:
发布订阅
一对多
生活示例
点咖啡,点之后,等被叫
// 主题,接收状态变化,触发每个观察者
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
attach(observer) {
this.observers.push(observer)
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update()
})
}
}
// 观察者,等待被触发
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update() {
console.log(this.name+"update", "state: "+this.subject.getState())
}
}
// 测试代码
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)
测试结果:
前端常用场景
网页事件绑定
Promise
jQuery callbacks
nodejs自定义事件
nodejs中处理http请求,
多进程通讯
vue和react组件生命周期触发
vue 的watch
9.迭代器模式
class Iterator {
constructor(conatiner) {
this.list = conatiner.list
this.index = 0
}
next() {
if (this.hasNext()) {
return this.list[this.index++]
}
return null
}
hasNext() {
if (this.index >= this.list.length) {
return false
}
return true
}
}
class Container {
constructor(list) {
this.list = list
}
getIterator() {
return new Iterator(this)
}
}
// 测试代码
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while(iterator.hasNext()) {
console.log(iterator.next())
}
前端常用场景
jQuery 的each
ES6的Iterator
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)
// function each(data) {
// // 生成遍历器
// let iterator = data[Symbol.iterator]()
// // console.log(iterator.next()) // 有数据时返回 {value: 1, done: false}
// // console.log(iterator.next())
// // console.log(iterator.next())
// // console.log(iterator.next())
// // console.log(iterator.next()) // 没有数据时返回 {value: undefined, done: true}
// let item = {done: false}
// while (!item.done) {
// item = iterator.next()
// if (!item.done) {
// console.log(item.value)
// }
// }
// }
function each(data) {
for (let item of data) {
console.log(item)
}
}
each(arr)
each(nodeList)
each(m)