原始 1 2 3 4 5 6 7 8 9 let e1 = { name : "kerwin" , age : 18 }let e2 = { name : "tiechui" , age : 100 }
1.构造器模式 构造函数
1 2 3 4 5 6 7 8 9 10 function Employee (name, age ) { this .name = name this .age = age this .say = function ( ) { console .log (this .name + '-' + this .age ) } }let e1 = new Employee ("kerwin" , 100 )let e2 = new Employee ("tiechui" , 18 )
优点:复用代码
缺点:每个对象的方法都是独立的,但其实没必要,可以复用的
2.原型模式 js 专用,只有 js 有原型
可以把方法挂在原型上,这样就可以复用了
1 2 3 4 5 6 7 8 9 function Employee (name, age ) { this .name = name this .age = age }Employee .prototype .say = function ( ) { console .log (this .name + '-' + this .age ) }let e1 = new Employee ("kerwin" , 100 )let e2 = new Employee ("tiechui" , 18 )
es6 类 综合构造器模式和原型模式
1 2 3 4 5 6 7 8 9 10 11 12 13 class Employee { constructor (name, age ) { this .name = name this .age = age } say ( ) { console .log (this .name + '-' + this .age ) } }let e1 = new Employee ("kerwin" , 100 )
3.工厂模式 由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。
传一个参数进去,会根据这个参数,new一个对象返回。不用管过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class User { constructor (role, pages ) { this .role = role this .pages = pages } static UserFactory (role) { switch (role) { case "superadmin" : return new User ("superadmin" , ["home" , "user-manage" , "right-manage" , "news-manage" ]) case "admin" : return new User ("admin" , ["home" , "user-manage" , "news-manage" ]) case "editor" : return new User ("editor" , ["home" , "news-manage" ]) default : throw new Error ('参数错误' ) } } }let user = User .UserFactory ("editor" )
优点:只需要一个参数,就可以获取到需要的对象,无需知道创建的具体细节。
缺点:对象创建的逻辑写在类方法里,每次增加新的构造函数需要修改判断逻辑代码,需要维护。
使用场景:创建的对象数量较少,对象的创建逻辑不复杂。(如后台系统的权限管理)
4.抽象工程模式 相比工厂模式,返回的是类,而不是实例。
优点:更解耦,可以管理每一个类 。方便给每一个类添加自己的东西。
缺点:比较复杂,大型项目才会使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class User { constructor (name, role, pages ) { this .name = name this .role = role this .pages = pages } welcome ( ) { console .log ("欢迎回来" , this .name ) } dataShow ( ) { } }class SuperAdmin extends User { constructor (name ) { super (name, "superadmin" , ["home" , "user-manage" , "right-manage" , "news-manage" ]) } dataShow ( ) { console .log ('superadmin-datashow' ) } addRight ( ) { } addUser ( ) { } }class Admin extends User { constructor (name ) { super (name, "admin" , ["home" , "user-manage" , "news-manage" ]) } dataShow ( ) { console .log ('admin-datashow' ) } addUser ( ) { } }class Editor extends User { constructor (name ) { super (name, "editor" , ["home" , "news-manage" ]) } dataShow ( ) { console .log ('editor-datashow' ) } }function getAbstractUserFactory (role ) { switch (role) { case "superadmin" : return SuperAdmin case "admin" : return Admin case "editor" : return Editor default : throw new Error ("参数错误" ) } }let UserClass = getAbstractUserFactory ("editor" )let user = new UserClass ("kerwin" ) user.dataShow ()
5.建造者模式 建造者模式将一个复杂对象的构建层与其表示层相互分离,同样的构建过程可采用不同的表示。
工厂模式是为了创建对象实例或者类,关心的是创建的结果,而不关心创建的过程。
建造者模式关心的是创建这个对象的过程。
有一些元素构建的过程一致,可以创建一个类,在这个类中实现整个构建的过程,这样就可以复用构建的过程了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Navbar { init ( ) { console .log ("Navbar-init" ) } getData ( ) { console .log ("Navbar-getData" ) return new Promise (() => { setTimeout ((resolve ) => { resolve ('list-111' ) }, 1000 ); }) } render ( ) { console .log ("Navbar-render" ) } }class List { init ( ) { console .log ("List-init" ) } getData ( ) { console .log ("List-getData" ) return "navbar-111" } render ( ) { console .log ("List-render" ) } }
1 2 3 4 5 6 7 8 9 10 let navbar = new Navbar () navbar.init () navbar.getData () navbar.render ()let list = new List () list.init () list.getData () list.render ()
1 2 3 4 5 6 7 8 9 10 11 12 13 class Creator { async startBuild (builder ) { await builder.init () await builder.getData () await builder.render () } }const op = new Creator () op.startBuild (new Navbar ()) op.startBuild (new List ())
6.单例模式 保证一个类只有一个实例,并提供一个访问它的全局访问点
主要解决一个全局使用的类频繁创建和销毁,占用内存
为什么不用全局变量,而要使用单例模式(因为全局变量容易被污染)
使用场景:vuex / redux;防抖节流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let Singleton = (function ( ) { let instance function User (name, age ) { this .name = name this .age = age } return function (name, age ) { if (!instance) { instance = new User (name, age) } return instance } })()let a = Singleton ("kerwin" , 100 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Singleton { constructor (name, age ) { if (!Singleton .instance ) { this .name = name this .age = age Singleton .instance = this } return Singleton .instance } }console .log (new Singleton ("kerwin" , 100 ) === new Singleton ("xm" , 18 ))
7.装饰器模式 对已有功能进行拓展,不改变原有代码 ,对其他业务产生影响。常用于添加前置和后置函数。
使用场景:路由守卫;axios(请求/响应)拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Function .prototype .before = function (beforeFn ) { let _this = this return function ( ) { beforeFn.apply (this , arguments ) return _this.apply (this , arguments ) } }Function .prototype .after = function (afterFn ) { let _this = this return function ( ) { let result = _this.apply (this , arguments ) afterFn.apply (this , arguments ) return result } }function test ( ) { console .log ('函数本身' ) }let test1 = test.before (function ( ) { console .log ('前置函数' ) }).after (function ( ) { console .log ('后置函数' ) })test1 ()
8.适配模式 将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
使用场景:axios库在web端和node端都可以使用,实际使用了适配器的理念。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class TencentMap { show ( ) { console .log ("开始渲染腾讯地图" ) } }class BaiduMap { display ( ) { console .log ("开始渲染百度地图" ) } }class TencentAdapator extends TencentMap { constructor ( ) { super () } display ( ) { this .show () } }function renderMap (map ) { map.display () }renderMap (new TencentMap ())renderMap (new BaiduMap ())
9.策略模式 替代 if-else,if-else 难维护
策略模式将算法封装,把使用算法的责任和算法的实现分割开来。拓展性强。
1 2 3 4 5 6 7 8 9 10 11 12 let strategy = { "A" : (salary ) => salary * 4 , "B" : (salary ) => salary * 3 , "C" : (salary ) => salary * 2 , }function calBonus (level, salary ) { return strategy[level](salary) }calBonus ("A" , 10000 )
其实在项目中已经经常使用这种思想了。
10.代理模式 proxy,为其他对象提供一种代理以控制对这个对象的访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Star { play ( ) { console .log ("演戏" ); } }class StarProxy { constructor ( ) { this .superStar = new Star () } talk (price ) { if (price >= 10000 ) { this .superStar .play () } else { throw new Error ("价钱不合适" ) } } }let jr = new StarProxy () jr.talk (10000 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let star = { name : 'tiechui' , workprice : 10000 }let proxy = new Proxy (star, { get (target, key ) { if (key === 'workprice' ) { console .log ("访问了" ) } return target[key] }, set (target, key, value ) { if (key === "workprice" ) { console .log ("设置了" ) if (value > 10000 ) { console .log ("可以合作" ) } else { throw new Error ("价钱不合适" ) } } } })
11.观察者模式 包含观察目标和观察者两类对象
一个目标可以有任意与之相依赖的观察者
一旦观察目标的状态发生改变,所有的观察者都将得到通知并被自动更新性能
解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题
使用场景:导航栏?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Subject { constructor ( ) { this .observers = [] } add (observer ) { this .observers .push (observer) } notify ( ) { this .observers .forEach ((item ) => { item.update () }) } remove (observer ) { this .observers = this .observers .filter ((item ) => { return item !== observer }) } }class Observer { constructor (name ) { this .name = name } update ( ) { console .log (this .name ) } }const subject = new Subject ()const observer1 = new Observer ('kerwin' )const observer2 = new Observer ('tiechui' ) subject.add (observer1) subject.add (observer2)setTimeout (() => { subject.remove (observer1) }, 1000 );setTimeout (() => { subject.notify () }, 2000 );
优点:目标者与观察者,功能耦合度降低,专注自身功能逻辑;观察者被动接受更新,时间上解耦,实时接受目标者更新状态 。
缺点:不能对事件通知进行细分管控,如”筛选通知“,”指定主题事件通知“。
12.发布订阅模式 观察者模式中,观察者和目标要相互知道
发布订阅模式中,发布者和订阅者不用互相知道,通过第三方实现调度 ,属于经过解耦合的观察者模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Event { events = {} emit (type, ...args ) { const listeners = this .events [type] for (const listener of listeners) { listener (...args) } } on (type, listener ) { this .events [type] = this .events [type] || [] this .events [type].push (listener) } once (type, listener ) { const callback = (...args ) => { this .off (type, callback) listener (...args) } this .on (type, callback) } off (type, listener ) { this .events [type] = this .events [type] || [] this .events [type] = this .events [type].filter ((item ) => { item !== listener }) } }const event = new Event () event.subscribe ("event1" , function (data ){ console .log (data) }) event.publish ("event1" , '123' )
比较观察者和发布订阅 观察者 观察者模式创建两个类,一个观察目标(subject)、一个观察者(observe),观察目标需要添加观察者,所以并没有解耦。
触发通知时要调用观察者类中预先定义好的更新方法(update)
由于update只有一个,所以无法实现细分管控。要实现的话需要多定义一些Observer类,这样维护起来很麻烦。
耦合度高,即需要知道每个观察者,又需要知道观察者类的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Subject { constructor ( ) { this .observers = [] } add (observer ) { this .observers .push (observer) } notify ( ) { this .observers .forEach ((item ) => { item.update () }) } remove (observer ) { this .observers = this .observers .filter ((item ) => { return item !== observer }) } }class Observer { constructor (name ) { this .name = name } update ( ) { console .log (this .name ) } }const subject = new Subject ()const observer1 = new Observer ('kerwin' )const observer2 = new Observer ('tiechui' ) subject.add (observer1) subject.add (observer2)setTimeout (() => { subject.remove (observer1) }, 1000 );setTimeout (() => { subject.notify () }, 2000 );
发布订阅 发布订阅模式只有一个Event类(PubSub),作为中间控制平台,只需要在这个平台上绑定事件,在需要使用的时候利用事件名触发事件即可。所以耦合度很低,只需知道事件名。
此外,事件名就可以完美地实现事件筛选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Event { events = {} emit (type, ...args ) { const listeners = this .events [type] for (const listener of listeners) { listener (...args) } } on (type, listener ) { this .events [type] = this .events [type] || [] this .events [type].push (listener) } once (type, listener ) { const callback = (...args ) => { this .off (type, callback) listener (...args) } this .on (type, callback) } off (type, listener ) { this .events [type] = this .events [type] || [] this .events [type] = this .events [type].filter ((item ) => { item !== listener }) } }const event = new Event () event.subscribe ("event1" , function (data ){ console .log (data) }) event.publish ("event1" , '123' )
13.模块(化)模式 模块化:能够使一个单独的对象拥有公共/私有的方法和变量,减少函数名冲突的可能;拆分大文件。
以前使用闭包实现变量私有化
1 2 3 4 5 6 7 8 9 10 11 12 13 const obj = (function ( ) { let count = 0 return { increase ( ) { return ++count }, decrease ( ) { return --count } } })
==es6模块化 ==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let count = 0 function increase ( ) { return ++count }function decrease ( ) { return --count }export default { increase, decrease }
1 2 3 4 <script type ="module" > import myObj from './test.js' </script >
ES13新增了#,可以在类中直接声明私有变量。
1 2 3 4 5 6 class Person { #name = "kerwin" }console .log (new Person ().#name)
14.桥接模式 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立的变化
使用场景:一个类存在两个或多个独立变化的维度,且这两个维度都需要进行拓展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function Audi (engine ) { this .engine = engine }Audi .prototype .loadEngine = function ( ) { this .engine .run () }function Benz (engine ) { this .engine = engine }Benz .prototype .loadEngine = function ( ) { this .engine .run () }function V6 ( ) { this .run = function ( ) { console .log ("V6发动机" ) } }function V8 ( ) { this .run = function ( ) { console .log ("v8发动机" ); } }let audi1 = new Audi (new V6 ())let audi2 = new Audi (new V8 ())let benz1 = new Benz (new V8 ()) audi1.loadEngine () audi2.loadEngine () benz1.loadEngine ()
优点:把抽象与实现隔离开,有助于独立管理各个组成部分
缺点:每使用一个桥接元素都要增加一次函数调用,系统的复杂程度提高
15.组合模式 组合模式在对象间形成树形结构
组合模式中基本对象和组合对象被一致对待
无须关心对象有多少层,调用时只需在根部进行调用
在树形结构的问题中,模糊了简单元素和复杂元素的概念。程序可以向处理简单元素一样来处理复杂元素,从而使客户程序与复杂元素的内部结构解耦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 const Folder = function (folder ) { this .folder = folder this .list = [] }Folder .prototype .add = function (res ) { this .list .push (res) }Folder .prototype .scan = function ( ) { console .log ("扫描文件夹" , this .folder ) for (let i = 0 ; i < this .list .length ; i++) { this .list [i].scan () } }const File = function (file ) { this .file = file }File .prototype .scan = function ( ) { console .log ("扫描文件" , this .file ) }let rootFolder = new Folder ("root" )let htmlFolder = new Folder ("html" )let cssFolder = new Folder ("css" )let jsFolder = new Folder ("js" )let html1 = new File ("html1" )let html2 = new File ("html2" )let css1 = new File ("css1" )let css2 = new File ("css2" )let js1 = new File ("js1" )let js2 = new File ("js2" ) rootFolder.add (htmlFolder) rootFolder.add (cssFolder) rootFolder.add (jsFolder) htmlFolder.add (html1) htmlFolder.add (html2) cssFolder.add (css1) cssFolder.add (css2) jsFolder.add (js1) jsFolder.add (js2) rootFolder.scan ()