Javascript 设计模式系统讲解与应用

274 阅读10分钟

一、前言

1、论工程师的设计能力

1、3年工作经验,面试必考设计能力
2、成为项目技术负责人,设计能力是必要基础
3、从写好代码,到做好设计,设计模式是必经之路

二、 面向对象

1、搭建开发环境

1、安装nodejs
2、安装webpack webpack-cli、

npm install webpack webpack-cli

3、初始化项目

npm init
4、修改package.json文件script脚本执行内容

"scripts": {
    "dev": "webpack --config webpack.dev.config.js --mode development"
  },
5、配置webpack.dev.config.js

6、安装 webpack-dev-server / html-webpack-plugin

npm install -D webpack-dev-server / html-webpack-plugin
7、安装ES6转码环境

npm install -D 
babel-core 
babel-loader 
babel-polyfill 
babel-preset-es2015 
babel-preset-latest

2、 面向对象

1、概念
实例

2、三要素
  • 继承,子类继承父类
  • 封装,数据的权限和保密
  • 多态,同一接口不同实现

1、三要素-继承:
People是父类,公共的,不仅仅服务于Student
继承可以将公共方法抽离,提高复用,减少冗余

2、三要素-封装:

Public 完全开放
Protected 对子类开放
private对自己开发
ES6尚不支持,可以用typescript来演示

减少耦合,不该暴露的不外露
利于数据、接口的权限管理
ES目前不支持,一般认为_开头的属性是private


3、三要素-多态:
保持子类的开放性和灵活性
面向接口编程
(JS引用极少,了解即可)

 为什么使用面向对象
  • 1、程序执行:顺序、判断、循环----结构化
  • 2、面向对象---- 数据结构化
  • 3、对于计算机,结构化的才是最简单的
  • 4、编程应该 简单&抽象

3、 设计原则

1、何为设计:

  • 1、即按照哪种思路或者标准来实现功能
  • 2、功能相同,可以有不同设计方案来实现
  • 3、伴随着需求增加,设计的作用才能体现出来

 《UNIX/LINUX设计哲学》
大准则
  • 1、小即美
  • 2、让每个程序只做好一件事
  • 3、快速搭建原型
  • 4、舍弃高效率而取可移植性
  • 5、采用纯文本存储数据
  • 6、充分利用软件的杠杆效应(软件复用)
  • 7、使用shell脚本来提高杠杆效应和可移植性
  • 8、避免强制性的用户界面
  • 9、让每个程序都成为过滤器


小准则
  • 1、允许用户定制环境
  • 2、尽量使操作系统内核小而轻量
  • 3、使用小写字母并尽量简短
  • 4、沉默是金
  • 5、各部分之和大于整体
  • 6、寻求90%的解决方案


 2、五大设计原则 SOLID
  • 1、S 单一职责原则
  • 2、O 开放封闭原则
  • 3、L 李氏置换原则
  • 4、I 接口独立原则
  • 5、D 依赖倒置原则


1、S 单一职责原则:
一个程序只做好一件事
如果功能过于复杂就才分开,每个部分保持独立

2、 O 开放封闭原则:
对扩展开放,对修改封闭
需求增加需求是,扩展新代码,而非修改已有代码
这是软件设计的终极目标


 3、L 李氏置换原则:
子类能覆盖父类
父类能出现的地方子类就能出现
JS中使用较少(弱类型&继承使用较少)


4、 I 接口独立原则:
保持接口的单一独立,避免实现“胖接口”
JS中没有接口(typescript除外),使用较少
类似单一职责原则,这里更关注接口


5、D 依赖倒置原则:
面向接口编程,依赖于抽象而不依赖于具体
使用方只关注接口而不关注具体类的实现
JS中使用较少(没有接口&泛型)

promise 讲解 SO

function loadImg(src){
   return new Promise(function(resolve,reject){
      let img = document.createElement('img);
      img.onload = function(){
         resolve(img)
      }
      img.error = function(){
         reject("图片加载失败")
      }
      img.src=src;
   }
)}

 3、从设计到模式

三、 设计模式分类

23种设计模式

 1、创建型
  • 1、工厂模式
  • 2、单例模式
  • 3、原型模式
2、结构模式
  • 1、适配器模式
  • 2、装饰器模式
  • 3、代理模式
  • 4、外观模式
  • 5、桥接模式
  • 6、组合模式
  • 7、享元模式


3、行为模式
行为模式1
  • 1、策略模式
  • 2、模板方法模式
  • 3、观察者模式
  • 4、迭代器模式
  • 5、职责模式
  • 6、命令模式

行为模式2
  • 1、备忘录模式
  • 2、状态模式
  • 3、访问者模式
  • 4、终结者模式
  • 5、解释器模式


四、工厂模式

 设计原则

1、构造函数和创建函数分离
2、符合开放封闭原则

1、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){
   }
}
window.$ = function(selector){
   return new jQuery(selector)
}
 2、React.createElement

class Vnode(tag,attrs,children){
}

React.createElement = function(tag,attrs,children){
   return new Vnode(tag,attrs,children);
}
3、vue异步组件


五、单例模式

 介绍
1、系统中被唯一使用
2、一个类只有一个实例

1、jQuery

// jQuery只有一个$
if(window.jQuery!=null){
   return window.jQuery;
}else{
   // 初始化
}

2、购物车 登录框

3、Vuex和Redux的store

 设计原则验证

1、符合单一职责原则,只实例化唯一的对象
2、没法具体开放闭合原则,但是绝对不违反开放封闭原则

六、适配器模式


介绍
1、旧接口格式和使用不兼容
2、中间加一个适配器转换接口

 设计原则验证
1、将旧接口和使用者进行分离
2、符合开闭原则

七、装饰器模式

介绍
1、为对象添加新功能
2、不改变其原有的结构和功能

 1、ES7 decorator

npm install -D babel-plugin-transform-decorators-legacy
配置.babelrc文件

{
    "presets": [
        [
            "latest",
            {
                "es2015": false
            }
        ]
    ],
    "plugins": [
        "transform-decorators-legacy"
    ]
}
2、装饰类

@testDecclass Demo{}
function  testDemo(target){
   target.isDec = true;
}
alert(Demo.isDec)
3、装饰类 mixin示例

function mixins(...list) {
    return function (target) {
        Object.assign(target.prototype, ...list);
    }
}
const Foo = {
    foo() {
        alert("foo");
    }
}
@mixins(Foo)
class MyClass {}
let obj = new MyClass();
obj.foo();// foo
4、装饰方法 1

function readonly(target, name, descriptor) {
    // descriptor 属性描述(Object.defineProperty中会用到),
    // {
    //     value:  specifiedFunction,
    //     enumerable:false,
    //     configurable:true,
    //     writable: true
    // }
    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());// AB
p.name = function () { }; // 这里会报错,因为name设计只读属性

5、 装饰方法 2

class Math {
    @log
    add(a, b) {
        return a + b;
    }
}
const math = new Math();
const result = math.add(2, 4);// 执行add时,会自动打印日志,因为有@log装饰器
console.log("result", result);

6、core-decorators

第三方开源lib

提供常用的装饰器


7、 设计原则验证

1、将现有对象和装饰器进行分离,两者独立存在
2、符合开放封闭原则

八、代理模式

场景

1、网页事件代理

var div = document.getElementById("div");
div.addEventListener("click",function(e){
   var target = e.target;
   if(target.nodeName==='A'){
      alert(target.innerHTML)
   }
})
2、jQuery $.proxy

// 变量赋值
$("#div).click(function(){
   var _this = this;
   setTimeout(()=>{
      _this.addClass("red)
   },1000)
})

// $.proxy$("#div).click($.proxy(function(){
   setTimeout(()=>{
      this.addClass("red)
   },1000)},this))
3、ES6 Proxy

new Proxy(target,{
   get:function(target,key){
   }
   set:function(target,key,val){
   }
})

4、设计原则验证
1、代理和目标类分离,隔离开目标类和使用者
2、符合开放封闭原则


5、外观模式

1、为子系统中的一组接口提供了一个高层接口
2、使用者使用这个高层接口

function bindEvent(elem,type,selector,fn){
   if(fn===null){
      fn = selector;
      selector = null;
   }
}
bindEvent(elem,'click','#div',fn);
bindEvent(elem,'click',fn);

6、设计原则

1、不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用

九、 观察者模式

  介绍

  • 1、发布订阅
  • 2、一对多
1、网页事件绑定

var div  = window.getElementById("div");
div.addEventListener("click",function(){
   console.log("click")
})
 2、Promise

let promise = new Promise(()=>{
   resolve()
})
promise.then((res)=>{
    
})
 3、jQuery callbacks

let callbacks = $.Callbacks();
callbacks.add(info=>{
   console.log(info)
})
callbacks.fire("执行")
 4、nodejs自定义事件

const EventEmitter = require("events").EventEmitter;
const emitter1 = new EventEmitter();
emitter1.on("some",(info)=>{
   console.log(info)
})
emitter1.emit("some")
5、nodejs自定义事件

var fs = require("fs");
var readStream = fs.createReadStream("./data/file.txt")
var length = 0;
readStream.on("data", function (chunk) {
    length += chunk.toString().length
})
readStream.on("end", function () {
    console.log(length)
})

 其他场景
1、nodejs中:处理http请求;多进程通信
2、vue和react组件生命周期
3、vue watch


十、 迭代器模式

介绍

  • 1、顺序访问一个集合
  • 2、使用者无需知道集合的内部结构(封装)

 示例 jQuery $()

1、数组遍历

var arr = [4,32,432,43];
arr.forEach((item)=>{})

2、JS DOM 遍历

var nodeList =  document.getElementByTagNames("p");
nodeList.map(item=>{})

// jQuery 对象遍历
var $p = $("p");
$p.each((key,p)=>{})
3、自己封装

class Iterator {
    constructor(container) {
        this.container = container;
        this.index = 0;
        console.log(container)
    }
    next() {
        if (this.hasNext()) {
            return this.container.list[this.index++]
        }
        return null;
    }
    hasNext() {
        if (this.index >= this.container.list.length) {
            return false
        }
        return true;
    }
}
class Container {
    constructor(list) {
        this.list = list;
    }
    //生成遍历器   
 getIterator() { 
       return new Iterator(this)
    }
}
let arr = [432, 432, 42, 34];
let container = new Container(arr);
let iterator = container.getIterator();
while (iterator.hasNext()) {
    console.log(iterator.next())
}

4、 jQuery each

function each(data){
   var $data = $(data);
   $data.each((key,p)=>{
      console.log(key,p)
   }
)}

5、ES6 Iterator
  • 1、ES6语法中,有序集合的数据类型已经有很多
  • 2、Array Map String TypeArray arguments NodeList
  • 3、需要一个统一的接口来遍历多有数据类型
  • 4、(Object不是有序集合,可以用Map代替)
  • 5、以上数据类型都有[Symbol.iterator]属性
  • 6、属性值是函数,执行函数返回一个迭代器
  • 7、这个迭代器有的方法就可以顺序迭代子元素
  • 8、可运行Array.prototype[Symbol.iterator]来测试

6、 for of
带有Iterator遍历器的可以用

7、 ES6 与Generator
  • 1、Iterator的价值不限于上述几个类型的遍历
  • 2、还有Generator函数的使用
  • 3、即只要返回的数据符合Iterator接口的要求
  • 4、即可以使用Iterator语法,这就是迭代器模式


十一、状态模式

 介绍
1、一个对象有状态变化
2、每次状态变化都会触发一个逻辑
3、不能总是if else 来控制

1、 写一个简单的Promise


十二、其他设计模式


 创建型设计模式
  1. 原型模式

 结构型设计模式

  1. 桥接模式
  2. 组合模式
  3. 享元模式
 行为型设计模式
  1. 策略模式
  2. 模板方法模式
  3. 职责模式
  4. 命令模式
  5. 备忘录模式
  6. 中介者模式
  7. 访问者模式
  8. 解析器模式

 1、原型模式

基于原型,创建一个新的对象
  • 1、clone自己,生成一个新对象
  • 2、java默认有clone接口,不用自己实现

 1、Object.create

基于原型对象创建一个新的对象

2、对比JS中的原型prototype
  • 1、prototype可以理解为ES6 class的一种底层原理
  • 2、而class是实现面向对象的基础,并不是服务于某个模块
  • 3、若干年后ES普及,大家可能会忽略掉prototype
  • 4、但是Object.create却会长久保留

2、桥接模式

1、用于把抽象化与实现解耦
2、使得二者可以独立变化

3、组合模式

1、生成树形结构,表示‘整体-部分’关系
2、让整体和部分具有一致的操作方式


4、 享元模式

1、共享内存(主要考虑内存,而非效率)
2、相同的数据,共享使用
3、JS中未找到经典应用场景

5、演示模式

无线下拉列表,将事件代理到高层级节点上
如果都绑定到‘<a>’标签,对内存开销太大

<div id='div'>
 <a>
  <!--无限下拉列表  -->
</div>
<script>
   var div1 = document.getElementById("div")
    div.addEventListener("click",function(e){
      var target = e.target;
      if(e.nodeName === 'A'){
         console.log(target.innerHTML)
      }
    })
</script>

设计原则认证

1、将相同的部分抽象出来

2、符合开放封闭原则

6、策略模式

1、不同策略分开处理
2、避免出现大量的if else或者 switch case
3、JS中未找到经典例子

8、模板方法模式


8、职责链模式

1、一个操作可能分为多个职责角色来完成
2、把这些角色都分开,然后用一个链串起来
3、将发起者和各个处理着进行隔离

9、  命令模式

1、执行命令时,发布者和执行者分开
2、中间加入命令对象,作为中转站

// 接收者class Receiver {
    exec() {
        console.log("执行") 
   }
}
// 命令者
class Command {
    constructor(receiver) {
        this.receiver = receiver;
    }
    cmd() {
        console.log("执行命令");
        this.receiver.exec();
    }
}
// 触发者class Invoker {
    constructor(command) {
        this.command = command;
    }
    invoke() {
        console.log("开始");
        this.command.cmd()
    }
}
// 执行者
let receiver = new Receiver();
// 中间命令者
let command = new Command(receiver);
//  发布命令者
let invoker = new Invoker(command);
invoker.invoke()


10、 富文本编辑器

1、各个按钮实现的功能都是通过命令模式来执行


11、备忘录模式

1、随时记录一个对象的状态变化
2、随时可以恢复之前的某个状态(如撤销功能)
3、未找到JS中经典应用,除了一些工具,如编辑器

12、中介中模式

1、将各个关联对象通过中介者隔离
2、符合开放闭合原则



13、 访问者模式

1、将数据操作和数据结构进行分离
2、使用场景不多



14、解释器模式

1、描述语言语法如何定义,如何解释和编译
2、用于专业场景