Front-end Interview Road Six (Шаблоны проектирования Javascript)

Шаблоны проектирования

Принципы проектирования (SOLID)

Модель единой ответственности (S)

  • Программа делает только одно
  • Если функция слишком сложна, разделите ее и оставьте каждую часть независимой.

Принцип замещения в стиле Ли (L)

  • Подклассы могут охватывать родительские классы
  • Там, где может появиться родительский класс, может появиться дочерний класс
  • Меньше используется в JS (менее используется слабая типизация и наследование)

Открытый закрытый принцип (O)

  • Открыт для расширения закрыт для модификации
  • Расширяйте новый код вместо изменения существующего кода при добавлении требований
  • Конечная цель разработки программного обеспечения

Принцип разделения интерфейса (I)

  • Держите интерфейс единым и независимым, чтобы избежать «толстого интерфейса».
  • Нет интерфейса на JS (кроме машинописного), используется реже
  • Как и в случае с одиночной ответственностью, здесь больше связан с интерфейсом.

Принцип инверсии зависимостей (D)

  • Интерфейсно-ориентированное программирование, основанное на абстракциях, а не на конкретном
  • Забота потребителей касается не только интерфейса для достижения определенного класса
  • Меньше используется в JS (без интерфейсов и слабой типизации)

Шаблоны проектирования

заводской узор

  • Инкапсулируйте новую операцию отдельно
  • При столкновении с новым необходимо учитывать, следует ли использовать заводской шаблон

Пример

Вы идете покупать гамбургеры, заказываете и забираете еду напрямую, сами не приготовите
Магазины должны «упаковывать» работу по приготовлению бургеров и хорошо доставлять их непосредственно покупателям.

Диаграмма классов UML:

Пример кода:

class Product {
    constructor(name) {
        this.name = name;
    }
    init() {
        console.log('init')
    }
    fn1() {
        console.log('fn1')
    }
    fn2() {
        console.log('fn2')
    }
}

class Creator {
    create(name) {
        return new Product(name)
    }
}

let create = new Creator();
let p = create.create('p')
p.init()
p.fn1()
p.fn2()

Сценарии применения

  • jQuery:

$('div')а такжеnew $('div')Какая разница?

  • Во-первых: проблемы с написанием, цепочка операций jQuery станет кошмаром
  • Во-вторых: как только имя jQuery изменится, это будет иметь катастрофические последствия.
//仿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() {
        console.log('append');
    }
    addClass() {
        console.log('addClass')
    }

}

window.$ = function(selector) {
    return new jQuery(selector);
}

var $p = $('p')
console.log($p)
console.log($p.addClass)

  • React.crateElement:
var profile = <div>
    <img src="avater.png" className="profile"/>
    <h3>{[user.firstName,user.lastName].join('')}</h3>
    </div>;

После компиляции:

var profile = React.createElement("div",null,
    React.createElement("img",{src:"avater.png",className:"profile"}),
    React.createElement("h3",null,[user.firstName,user.lastName].join(" "))
);
//源码实现
class vnode(tag, attrs, children) {
    //...省略内部代码...
}

React.createElement = function(tag,attrs,children){
    return new vnode(tag,attrs,children)
}
  • Асинхронные компоненты для Vue:
Vue.component('async-example', funciton(resolve, reject) {
    setTimeout(function() => {
        resolve({
            template: '<div>I am async!</div>'
        })
    }, 1000);
})

Проверка принципа проектирования:

  • Разделение конструктора и создателя
  • Соблюдайте принцип открытого и закрытого

одноэлементный шаблон

  • используется только в системе
  • В классе есть только один пример

 

Пример:

Окно входа, корзина

Традиционная UML-диаграмма

иллюстрировать

  • Шаблон singleton требует использования функций Java (частных)
  • Нет в ES6 (кроме машинописного текста)
  • Для демонстрации содержимого диаграммы UML можно использовать только код Java (наконец-то реализованный в замаскированном js)

демонстрация кода

Java-версия демонстрации одноэлементного шаблона

public class SingleObject{
     //注意:私有化构造函数,外部不能new,只能内部new!!!!
     private SingleObject(){}
     //唯一被new出来的对象
     private SingleObject getInstance(){
         if(instance == null){
             //只new一次
             instance = new SingleObject();
         }
         return instance;
     }
     //对象方法
     public void login(username,password){
         System.out.println("login...")
     }
 }
 
 
 public class SingletonPatternDemo{
     public static void main(String[] args){
        //不合法的构造函数
        //编译时报错:构造函数 SingleObject()是不可见的!!!
        //SingleObject object = new SingleObject();
        //获取唯一可用的对象
        SingleObject object = SingleObject.getInstance();
     }     
}

Javascript-версия демонстрации одноэлементного шаблона

class SingleObject {
    login() {
        console.log('login...')
    }
}

//静态方法
SingleObject.getInstance = (function() {
    let instance
    return function() {
        if (!instance) {
            instance = new SingleObject();
        }
        return instance;
    }
})()

var login = SingleObject.getInstance().login();

Недостатки одноэлементного шаблона в javascript:

Если вы принудительно создадите новый, об ошибке не будет сообщено:

var loginnew = new SingleObject();
loginnew.login()

тестовое задание

//注意这里只能用静态函数getInstance,不能new SingleObject()!!!
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{
    //初始化...
}
//引用多少次都只有一个'$'
  • хранить в vuex и redux

  • корзина, окно входа

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()

let login2 = LoginForm.getInstance()
//lgoin2.show() //登录框已经显示
login2.hide()

console.log(login1 === login2)

Проверка принципа проектирования

  • В соответствии с принципом единой ответственности создавайте экземпляры только уникальных объектов.
  • Конкретного принципа открытости и закрытости нет, но он точно не нарушает принцип открытости и закрытости.

режим адаптера

  • Старый формат интерфейса не совместим с пользователем
  • Добавьте интерфейс преобразования адаптера посередине

Пример

преобразование адаптера macbookpro Страны с розетками не унифицированы и нуждаются в конверсионной головке.

UML Diagram

демо

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,使用方式如下:
ajax({
    url:'/getDate',
    type:'Post',
    dataType:'json',
    data:{
        id:123
    }
})
.done(function(){})
//但因为历史原因,代码中全都是:
//$.ajax({...})

Решение:

//做一层适配器
var $ = {
    ajax:function(options){
        return ajax(options)
    }
}
  • vue computed
<div id="example">
    <p>Original message:"{{message}}"</p>
    <p>Computed reversed message:"{{reversedMessage}}"</p>
</div>

var vm = new Vue({
    el:"#example",
    data:{
        mesage:'Hello'
    },
    computed:{
        //计算属性的getter
        reversedMessage:function(){
            //'this'指向vm实例
            return this.message.split('').reverse().join('')
        }
    }
})

Проверка принципа проектирования

  • Разделение старых интерфейсов и потребителей
  • Соблюдайте принцип открытого и закрытого

Шаблон декоратора

  • Добавить новые функции на объект
  • Не изменяет свою первоначальную структуру и функцию

Пример:

телефонный чехол

Диаграмма классов UML

демонстрация кода

class Circle {
    draw() {
        console.log('画一个圆形')
    }
}

class Decorator {
    constructor(circle) {
        this.circle = circle
    }
    draw() {
        this.circle.draw()
        this.setRedBorder(circle)
    }
    setRedBorder(circle) {
        console.log('设置红色边框')
    }
}

let circle = new Circle();
circle.draw()

let decorator = new Decorator(circle)
decorator.draw()

сцены, которые будут использоваться

  • декораторы ES7
@testDec
class Demo {
    //...
}

function testDec(target) {
    target.isDec = true;
}
alert(Demo.isDec);

Принцип декоратора

@decorator
class A {}

//等同于
class A{}
A = decorator(A)||A;

можно добавить параметры

function testDec(isDec){
    return function(target){
        target.isDec = isDec;
    }
}

@testDec(true)

class Demo{
    //....
}
alert(Demo.isDec) //true
function mixin(...list) {
    return function(target) {
        Object.assign(target.prototype, ...list)
    }
}

const Foo = {
    foo() { alert('foo') }
}

@mixin(Foo)
class myClass() {}

let obj = new myClass();
obj.foo() //'foo'

Способ украшения - пример 1

class Person {
    constructor() {
            this.first = 'A'
            this.last = 'B'
        }
        //装饰方法
    @readonly
    name() {
        return `${this.first} ${this.last}`
    }
}

var p = new Person()
console.log(p.name())   //p.name=function(){} //这里会报错,因为name是只读属性

function readonly(target, name, descriptor) {
    //descriptor 属性描述对象(Object.defineProperty中会用到),原来的值如下
    //{
    //  value:specifiedFunction,
    //  enumerable:false,
    //  configurable:true,
    //  writable:true   
    //}
    descriptor.writable = false;
    return descriptor;
}

Способ декорирования - пример 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)

function log(target, name, descriptor) {
    var oldvalue = descriptor.value;

    descriptor.value = function() {
        console.log(`calling ${name} with`, arguments);
        return oldvalue.apply(this, arguments)
    }
    return descriptor;
}
  • core-decorators
  • Сторонняя библиотека с открытым исходным кодом
  • Предоставляет часто используемые декораторы
//首先安装npm i core-decorators --save

//开始编码
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
    name() {
        return 'zhang'
    }
}

let p = new Person()
alert(p.name())
    //this funciton will be removed in future Vue.version
    //也可以自己定义@deprecate("即将废用")
    //也可以自己定义@deprecate("即将废用",{url:"www.imooc.com"})

Проверка принципа проектирования

  • Разделите существующий объект и декоратор, два существуют независимо
  • Соблюдайте принцип открытого и закрытого

прокси-режим

  • У пользователя нет разрешения на доступ к целевому объекту
  • Добавить прокси посередине, сделать авторизацию и управление через прокси

Пример:

  • Интернет
  • звездный агент

UML

демонстрация кода

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 proxyImg = new ProxyImg('1.png')
proxyImg.display()

Сцены

  • Брокер веб-событий
 var div1 = document.getElementById('div1')
 div1.addEventListener('click', funtion(e) {
    console.log(e)
    var target = e.target
    if (target.nodeName === "A") {
        alert(target.innerHtml)
    }
})
  • jQuery $.proxy
$('#div1').click(function() {
    //this符合期望
    $(this).addClass('red')
})
$('#div1').click(function() {
    setTimeout(function() {
        //this不符合期望
        $(this).addClass('red')
    }, 1000);
})

//可以用如下方式解决
$('#div1').click(function() {
    var _this = this
    setTimeout(funciton() {
        //_this符合期望
        $(_this).addClass('red')
    }, 1000)
})

или с $.proxy

//但推荐用$.proxy解决,这样就少定义一个变量
$('#div1').click(function() {
    setTimeout($.proxy(function() {
        //this符合期望
        $(this).addClass('red')
    },this), 1000)
})
  • ES6 Proxy
//明星
let star = {
    name: "zhangxx",
    age: 25,
    phone: '13910733521',
}

//经纪人
let agent = new Proxy(star, {
    get: function(target, key) {
        if (key === 'phone') {
            //返回经纪人自己的手机号
            return '13838383838'
        }
        if (key === "price") {
            //明星不报价,经纪人报价
            return 120000
        }
        return target[key]
    },
    set: function(target, key, val) {
        if (key === 'customPrice') {
            if (val < 100000) {
                throw new Error("价格太低")
            } else {
                target[key] = val
                return true
            }
        }
    }
})

console.log(agent.name)
console.log(agent.phone)
console.log(agent.age)
console.log(agent.price)

agent.customPrice = 150000;
console.log('agent.customPrice', agent.customPrice)

Проверка принципа проектирования

  • Класс агента отделен от целевого класса, а целевой класс и пользователь изолированы.
  • Соблюдайте принцип открытого и закрытого

Режим прокси против режима адаптера

  • Режим адаптера: предоставить другой интерфейс (например, другую версию штекера, нельзя использовать)
  • Режим прокси: предоставить точно такой же интерфейс (не авторизованный для использования)

Шаблон прокси против шаблона декоратора

  • Режим декоратора: расширенные функции, исходные функции остаются без изменений и могут использоваться напрямую
  • Режим прокси: прямое нацеливание (отображение) исходной функции, но после ограничения или кастрации

Внешний вид Режим

  • Предоставляет высокоуровневый интерфейс для набора интерфейсов в подсистеме.
  • Пользователь использует этот высокоуровневый интерфейс

Пример:

Идти в больницу к врачу

Диаграмма классов UML

демонстрация кода

function bindEvent(elem,type,selector,fn){
    if(fn == null){
        fn = selector
        selector = null
    }
}

//调用
bindEvent(elem,'click','#div1',fn)
bindEvent(elem,'click',fn)

Проверка принципа проектирования

  • Не соответствует принципу единой ответственности и открытому закрытому принципу, поэтому следует использовать с осторожностью, не злоупотреблять

Шаблон наблюдателя

  • Опубликовать и подписаться
  • Один ко многим (Н)

Пример

  • Закажи кофе и жди, пока тебя позовут

Диаграмма классов UML

Один из самых важных шаблонов в дизайне интерфейса

демонстрация кода

//保存状态,状态变化之后触发所有观察者
class Subject {
    constructor() {
        this.state = 0
        this.observers = []
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
        this.notifyAllObervers()
    }
    notifyAllObervers() {
        this.observers.forEach(observer => {
            observer.update()
        })
    }
    attach(observer) {
        this.observers.push(observer)
    }
}

//观察者
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 subject = new Subject();
let obs1 = new Observer('o1', subject);
let obs2 = new Observer('o2', subject);
let obs3 = new Observer('o3', subject);

subject.setState(1)
subject.setState(2)

Сценарии применения

  • Привязка веб-событий

Все прослушиватели событий используют шаблон наблюдателя

<button id="btn1">btn</button>

<script>
    $('#btn1').click(function () {
        console.log(1)
    })
    $('#btn1').click(function () {
        console.log(2)
    })
    $('#btn1').click(function () {
        console.log(2)
    })
</script>
  • Promise
function loadImg(src) {
    var promise = new Promise(function(resolve, reject) {
        var img = document.createElement('img')
        img.onload = function() {
            resolve(img)
        }
        img.onerror = function() {
            reject('图片加载失败')
        }
        img.src = src
    })
    return promise
}

var src = "https://www.xxx.com/img/dafdafdfdafdsafd.png"
var result = loadImg()
result.then(function(img){
    console.log('width',img.width)
}).then(function(img){
    console.log('width',img.height)
})

  • jQuery callbacks
var callbacks = $.Callbacks() //注意大小写
callbacks.add(function() {
    console.log('fn1', info)
})
callbacks.add(function() {
    console.log('fn2', info)
})
callbacks.add(function() {
    console.log('fn3', info)
})
callbacks.fire('gogoogogo')
callbacks.fire('fire')
  • пользовательское событие nodejs

cosnt EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
emitter1.on('some', () => {
    //监听some事件
    console.log('some events is occured 1')
})

emitter1.on('some', () => {
        //监听some事件
        console.log('some events is occured 2')
    })
    //触发some事件
emitter1.emit('some')
const EventEmitter = require('events').EventEmitter

//任何构造函数都可以继承 EventEmitter的方法on emit

class Dog extends EventEmitter {
    constructor(name) {
        super()
        this.name = name
    }
}

var simon = new Dog('simon')
simon.on('bark', function() {
    console.log(this.name, 'barked')
})
setInterval(() => {
    simon.emit('bark')
}, 500)

//Stream 用到了自定义事件

var fs = require('fs')
var readStream = fs.createReadStream('./data/file1.txt') //读取文件的stream

var length = 0
readStream.on('data', function(chunk) {
    length += chunk.toString().length
})

readStream.on('read', function() {
    console.log(length)
})

//readline用到了自定义事件

var readline = require('readline')
var fs = require('fs')

var rl = readline.createInterface({
    input: fs.createReadStream('./data/file1.txt')
});

var lineNum = 0
rl.on('line', function(line) {
    lineNum++
})
rl.on('close', function() {
    console.log('lineNum', lineNum)
})

  • В nodejs: обработка http-запросов, многопроцессорная связь
function serverCallback(req, res) {
    var method = req.method.toLowerCase() //获取请求方法
    if (method === 'get') {
        //省略3行,上文代码示例中处理GET请求的代码
    }
    if (method === 'post') {
        //接受post请求的内容
        var data = ''
        req.on('data', function() {
            //"一点一点"接收内容
            data += chunk.toString()
        })
        req.on('end', function() {
            //接收完毕,将内容输出
            res.writeHead(200, { 'Content-type': 'text/html' })
            res.write(data)
            res.end()
        })
    }
}
//parent.js
var cp = require('child_process')
var n = cp.fork('./sub.js')
n.on('message', function(m) {
    console.log('PARENT got message:' + m)
})
n.send({ hello: 'workd' })

//sub.js
process.on('message', function(m) {
    console.log('CHILD got message:' + m)
})

process.send({ foo: 'bar' })

  • Запуск жизненного цикла компонентов Vue и React
class login extends React.component {
    constructor(prop, context) {
        super(props, context)
        this.shouldComponentUpate = PureRenderMixin.shouldComponentUpate.bind(this);
        this.state = {
            checking: true
        }
    }
    render() {
        return ( 
            <div>
                <header title = "登录" history = { this.props.history } ></header> 
            </div >
        )
    }
    componentDidMount() {
        //判断是否已经登录
        this.doCheck()
    }
}
  • vue watch
var vm = new Vue({
    el: "#demo",
    data: {
        firstName: 'Foo',
        lastName: 'Bar',
        fullName: 'Foo Bar'
    },
    watch: {
        firstName: function(val) {
            this.fullName = val + '' + this.lastName
        },
        lastName: function(val) {
            this.fullName = this.firstName + '' + val
        }
    }
})

Проверка принципа проектирования

  • Субъект и наблюдатель разделены, не активируются, а пассивно контролируются, и они не связаны друг с другом.
  • Соблюдайте принцип открытого и закрытого

шаблон итератора

  • доступ к коллекции последовательно
  • Пользователю не нужно знать внутреннюю структуру коллекции (инкапсуляция)

Пример

  • Нет подходящего примера, jQuery демонстрирует
<p>jQuery each</p>
<p>jQuery each</p>
<p>jQuery each</p>
var arr = [1,2,3]
var nodeList = document.getElementsByTagName('p')
var $p = $('p')

//要对这三个对象进行遍历,要写三个遍历方法
arr.forEach(function(item){
    console.log(item)
})

//nodeList不是纯数组
var i,length = nodeList.length;
for(i;i<length;i++){
    console.log(nodeList[i])
}

$p.each(function(key,p){
    console.log(key,p)
})

//顺序遍历有序集合
//使用者不必知道集合的内部结构
function each(data) {
    var $data = $(data) //生成迭代器
    $data.each(function(key, value) {
        console.log(key,value)
    })
}

each(arr)
each(nodeList)
each($p)


Диаграмма классов UML

демонстрация кода

class Iterator {
    constructor(container) {
        this.list = container.list;
        this.index = 0;
    }
    next() {
        if (this.hasNext()) {
            return this.list[this.index++]
        }
    }
    hasNext() {
        if (this.index >= this.list.length) {
            return false;
        }
        return true;
    }
}

class Container {
    constructor(list) {
            this.list = list
        }
        //生成遍历器
    getIterator() {
        return new Iterator(this)
    }
}

let arr = [1, 2, 3, 4, 5, 6]
let container = new Container(arr)
let iterator = container.getIterator()
while (iterator.hasNext()) {
    console.log(iterator.next())
}

Сценарии применения

  • jQuery each
function each(data){
    var $data = $(data)   //生成迭代器
    $data.each(function(key,p){
        console.log(key,p)
    })
}
  • ES6 Iterator
  • В синтаксисе ES6 уже есть много типов данных для отсортированных наборов.
  • Array,Map,Set,String,TypedArray,argument,NodeList
  • Вышеупомянутые типы данных имеют[Symbol.Iterator]Атрибуты
  • Значение свойства является функцией, и выполнение функции возвращает итератор
  • Итератор будет иметь следующие последовательные методы итерации
  • Может бежатьArray.prototype[Symbol.iterator]тестировать
  • for...ofПотреблениеiterator
  • Итератор и генератор ES6
  • iteratorЗначение не ограничивается несколькими видами обхода книги, но иGeneratorиспользование функций
  • То есть до тех пор, пока возвращаемые данные соответствуютIteratorтребования к интерфейсу
  • готов использоватьIteratorсинтаксис, это шаблон итератора

Проверка принципа проектирования

  • Объект Iterator и целевой объект разделены
  • Итератор изолирует потребителя от целевого объекта
  • Соблюдайте принцип открытого и закрытого

режим состояния

  • Объект имеет изменения состояния
  • Каждое изменение состояния запускает логику
  • Не всегда можно использовать if...else для управления

Пример

Изменения в различных цветах светофора

Диаграмма классов UML

демонстрация кода

//状态
class State {
    constructor(state) {
        this.state = state
    }
    getState() {
        return this.state
    }
    handle(context) {
        console.log(`turn to ${this.state} light`)
        context.setState(this)
    }
}
//主体
class Context {
    constructor() {
        this.state = null
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
    }
}

let context = new Context()

let green = new State('green')
let yellow = new State('yellow')
let red = new State('red')

green.handle(context);
console.log(context.getState())

yellow.handle(context);
console.log(context.getState())

red.handle(context);
console.log(context.getState())

Сценарии применения

  • Конечный автомат
  • Конечное число состояний и изменения между ними, например светофор
  • Используйте библиотеку с открытым исходным кодом: javascript-state-machine
//状态机模型
import StateMachine from 'javascript-state-machine'

var fsm = new StateMachine({
    init: '收藏', //初始状态,待收藏
    transitions: [{
            name: 'doStore',
            from: '收藏',
            to: '取消收藏'
        },
        {
            name: 'deleteStore',
            from: '取消收藏',
            to: '收藏'
        }
    ],
    method: {
        //执行收藏
        onDoStore: function() {
            alert('收藏成功')
            updateText()
        },
        onDeleteStore: function() {
            alert('取消收藏')
            updateText()
        }
    }

})


var $btn = $('#btn');
    //点击事件
$btn.click(function() {
    if (fsm.is('收藏')) {
        fsm.doStore()
    } else {
        fsm.deleteStore()
    }
})

//更新文案
function updateText() {
    $btn.text(fsm.state)
}

//初始化文案
updateText()

  • Напишите простое обещание
  • Обещание — это конечный автомат
  • Обещания имеют три состояния: ожидание, выполнено, отклонено.
  • в ожидании -> выполнено или в ожидании -> отклонено, необратимые изменения
class MyPromise {
    constructor(fn) {
        this.successList = []
        this.failList = []

        fn(() => {
            //resolve函数
            fsm.resolve(this)
        }, () => {
            //reject函数
            fsm.reject(this)
        })
    }
    then(successFn, failFn) {
        this.successList.push(successFn)
        this.failList.push(failFn)
    }
}

//模型
var fsm = new StateMachine({
    init: 'pending',
    transitions: [{
        name: 'resolve',
        from: 'pending',
        to: 'fullfilled'
    }, {
        name: 'reject',
        from: 'pending',
        to: 'rejected'
    }],
    methods: {
        onResolve: function(state, data) {
            //参数state - 当前状态示例;data - fsm,resolve(xxx)执行时传递过来的参数
            data.successList.forEach(fn => fn());
        },
        onReject: function(satte, data) {
            //参数state - 当前状态示例;data-fsm.reject(xxx)执行时传递过来的参数
            data.failList.forEach(fn => fn())
        }
    }
})


function loadImg(src) {
    const promise = new MyPromise(function(resolve, reject) {
        let img = document.createElement('img');
        img.onload = function() {
            resolve(img)
        }
        img.onerror = function() {
            reject(img)
        }
        img.src = src
    })
    return promise
}

let src = "https://www.xxxx.com/dsadfa/dafdafd.png";
let result = loadImg(src);
result.then(function() {
    console.log('ok1')
}, function() {
    console.log('fail1')
})
result.then(function() {
    console.log('ok2')
}, function() {
    console.log('fail2')
})

Проверка шаблона проектирования

  • Разделите объект состояния и объект субъекта и обрабатывайте логику изменения состояния отдельно.
  • Соблюдайте принцип открытого и закрытого

Другие шаблоны проектирования

  • редко используемый
  • Не соответствует классической сцене

Тип создания:

  • режим прототипа

Структурный тип:

  • режим моста
  • Комбинированный режим
  • наилегчайший образец

поведенческий

  • режим стратегии
  • Шаблон метода шаблона
  • Схема цепочки ответственности
  • командный режим
  • режим заметки
  • модель посредника
  • шаблон посетителя
  • Режим переводчика

режим прототипа

  • клонируйте себя, создайте новый объект (новые накладные расходы относительно велики)
  • Java имеет интерфейс клонирования по умолчанию, вам не нужно реализовывать его самостоятельно

сцены, которые будут использоваться

Object.create

  • Object.create использует идею шаблона прототипа (хотя это не клон в java)
//基于一个原型创建一个对象
const prototype = {
    getName: function() {
        return this.first + '' + this.last;
    },
    say: function() {
        console.log('hello')
    }
}


//基于原型创建x
var x = Object.create(prototype)
x.first = 'A'
x.last = 'B'
console.log(x.getName())
x.say()

//基于原型创建y
var y = Object.create(prototype)
y.first = 'C'
y.last = 'D'
console.log(y.getName())
y.say()

Контрастные прототипы в JS

  • прототип можно понимать как основной принцип класса ES6
  • Класс является основой для объектно-ориентированной реализации, не обслуживающей определенный шаблон.

режим моста

  • Используется для отделения абстракции от реализации
  • так что два могут быть изменены независимо
  • Обычно используется в некоторых бизнесах

Сценарии применения

//普通实现
class ColorShape {
    yellowCircle() {
        //...画黄圆
    }
    redCircle() {
        //...画红圆
    }
    yellowTriangle() {
        //...画黄三角形
    }
    redTriangle() {
        //...画红三角形
    }
}
//测试
let cs = new ColorShape()
cs.yellowCircle()
cs.redCircle()
cs.yellowTriangle()
cs.redTriangle()

//桥接模式
class Color {
    constructor(color) {
        this.color = color;
    }
}
class Shape {
    constructor(name, color) {
        this.name = name;
        this.color = color;
    }
    draw() {
        //画图...
    }
}
//测试代码
let red = new Color("red")
let yellow = new Color("yellow")
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()

Цвета и графику можно свободно комбинировать с гораздо меньшей сложностью, а добавлять графику позже также легко.

Проверка принципа проектирования

  • Разделение абстракции и реализации, разделение
  • Соблюдайте принцип открытого и закрытого

Комбинированный режим

  • Структура связующего дерева, представляющая отношение «целое-часть»
  • Сделайте так, чтобы целое и части работали одинаково

Пример:

Сценарии применения

  • Vnode в виртуальном DOM имеет такую ​​форму, но тип данных простой.
  • Используйте JS для реализации управления папками меню, не классического приложения, а связанного с бизнесом
<div id="div1" class="container">
    <p>123</p>
    <p>456</p>
</div> 
{
    tag: 'div',
    attr: {
        id: 'div1',
        className: 'container'
    },
    children: [{
        tag: 'p',
        attr: {},
        children: ['123']
    }, {
        tag: 'p',
        attr: {},
        children: ['456']
    }]
}
  • Работа всего и отдельных узлов согласована
  • Структура данных всего и отдельных узлов также согласована.
  • Проверка принципа проектирования
  • Абстрагируйте работу целых и отдельных узлов
  • Соблюдайте принцип открытого и закрытого

наилегчайший образец

  • Общая память (в основном с учетом памяти, а не эффективности)
  • одни и те же данные, совместное использование

JS не нужно слишком много думать о накладных расходах памяти

демо

//无限下拉列表,将事件代理到高层次节点上
//如果都绑定到'<a>'标签,对内存开销大

<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <!--无限下拉列表-->
</div>

< script >
    var div1 = document.getElementById('div1')
    div1.addEventListener('click', function(e) {
        var target = e.target
        if (e.nodeName === 'A' {
            alert(target.innerHtml)
        })
    }) 
</script>

Проверка принципа проектирования

  • абстрагировать одни и те же части
  • Соблюдайте принцип открытого и закрытого

режим стратегии

  • Различные стратегии обрабатываются отдельно
  • Избегайте большого количества случаев if...else или switch...

демо

class User {
    constructor(type) {
        this.type = type
    }
    buy() {
        if (this.type === 'oridinary') {
            console.log('普通用户购买')
        } else if (this.type === 'member') {
            console.log('会员用户购买')
        } else if (this.type === 'vip') {
            console.log('vip用户购买')
        }
    }
}

//测试代码
var u1 = new User('oridinary')
u1.buy()
var u2 = new User('member')
u2.buy()
var u3 = new User('vip')
u3.buy()

Измените его на эту форму:

class OrdinaryUser {
    buy() {
        console.log('普通用户购买')
    }
}
class MemberUser {
    buy() {
        console.log('会员用户购买')
    }
}
class VipUser {
    buy() {
        console.log('vip用户购买')
    }
}

var u1 = new OrdinaryUser()
u1.buy()

var u2 = new MemberUser()
u2.buy()

var u3 = new VipUser()
u3.buy()

Проверка принципа проектирования

  • Различные стратегии, обрабатываемые отдельно, не смешиваясь вместе
  • Соблюдайте принцип открытого и закрытого

Шаблон метода шаблона и шаблон цепочки ответственности

Шаблон метода шаблона:

class Action {
    handle() {
        handle1();
        handle2();
        handle3();
    }
    handle1() {
        console.log('1')
    }
    handle2() {
        console.log('2')
    }
    handle3() {
        console.log('3')
    }
}

Схема цепочки ответственности

  • Одноэтапная операция может быть разделена на несколько ролей ответственности для выполнения
  • Разделите эти роли и свяжите их вместе цепочкой
  • Изолировать отправителя от каждого обработчика
Демо:
//请假审批,需要组长审批、经理审批、最后总监审批
class Action {
    constructor(name) {
        this.name = name;
        this.nextAction = null
    }
    setNextAction(action) {
        this.nextAction = action
    }
    handle() {
        console.log(`${this.name} 审批`)
        if (this.nextAction != null) {
            this.nextAction.handle()
        }
    }
}

let a1 = new Action('组长')
let a2 = new Action('经理')
let a3 = new Action('总监')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

Сценарии применения

Цепочки операций в JS

  • Существует множество комбинаций цепочки ответственности и бизнеса, и цепочки операций можно представить в JS.
  • Цепная операция jQuery, цепная операция Promise.then

Проверка принципа проектирования

  • Инициатор и каждый процессор изолированы
  • Соблюдайте принцип открытого и закрытого

командный режим

  • При выполнении команды издатель и исполнитель разделены
  • Добавьте командный объект посередине в качестве станции передачи

class Receiver {
    exec() {
        console.log('执行')
    }
}

class Command {
    constructor(receiver) {
        this.receiver = receiver
    }
    cmd() {
        console.log('触发命令')
        this.receiver.exec()
    }
}

class Invoke {
    constructor(command) {
        this.command = command;
    }
    invoke() {
        console.log('开始')
        this.command.cmd();
    }
}

let soldier = new Receiver()
let trumpeter = new Command(soldier)
let general = new Invoke(trumpeter)
general.invoke()

Сценарии применения

  • Работа редактора форматированного текста веб-страницы, браузер инкапсулирует объект команды
  • document.execCommand("bold")
  • document.execCommand("undo")

Проверка принципа проектирования

  • Объект команды отделяется от объекта выполнения и отделяется
  • Соблюдайте принцип открытого и закрытого

режим заметки

  • Запись изменений состояния объекта в любое время
  • Предыдущее состояние может быть восстановлено в любое время (например, функция отмены)

демо

редактор

//备忘类
class Memento {
    constructor(content) {
        this.content = content;
    }
    getContent() {
        return this.content;
    }
}

//备忘列表
class CareTaker {
    constructor() {
        this.list = [];
    }
    add(memento) {
        this.list.push(memento)
    }
    get(index) {
        return this.list[index]
    }
}

//编辑器
class Editor {
    constructor() {
        this.content = null
    }
    setContent(content) {
        this.content = content
    }
    getContent(content) {
        return this.content
    }
    saveContentToMemento() {
        return new Memento(this.content)
    }
    getContentFromMenmeto(memento) {
        this.content = memento.getContent()
    }
}

//测试代码
let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento()) //存储备忘录
editor.setContent('333')
careTaker.add(editor.saveContentToMemento()) //存储备忘录
editor.setContent('444')

console.log(editor.getContent())
editor.getContentFromMenmeto(careTaker.get(1)) //撤销
console.log(editor.getContent())
editor.getContentFromMenmeto(careTaker.get(0)) //撤销
console.log(editor.getContent())

Проверка принципа проектирования

  • Объект состояния отделен от пользователя и отделен
  • Соблюдайте принцип открытого и закрытого

модель посредника

демо

class Mediator {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
    setA() {
        let number = this.b.number;
        this.a.setNumber(number * 100);
    }
    setB() {
        let number = this.a.number;
        this.b.setNumber(number / 100);
    }
}

class A {
    constructor() {
        this.number = 0;
    }
    setNumber(num, m) {
        this.number = num;
        if (m) {
            m.setB()
        }
    }
}

class B {
    constructor() {
        this.number = 0;
    }
    setNumber(num, m) {
        this.number = num;
        if (m) {
            m.setA();
        }
    }
}


let a = new A();
let b = new B();
let m = new Mediator(a, b);

a.setNumber(100, m);
console.log(a.number, b.number);
b.setNumber(300, m);
console.log(a.number, b.number)

Проверка принципа проектирования

  • Изолировать каждый связанный объект через посредника
  • Соблюдайте принцип открытого и закрытого

шаблон посетителя

  • Разделение операций с данными и структур данных
  • Не много сценариев использования

Режим переводчика

  • Описать, как грамматика языка определяется, интерпретируется и компилируется
  • для профессионального использования

Комплексное приложение

Об интервью

  • Может назвать шаблон проектирования, на котором фокусируется курс

ежедневное использование

  • Поймите ключевые шаблоны проектирования и заставьте себя подражать и осваивать
  • Очень полезный шаблон проектирования, избирательно используемый в соответствии с бизнес-сценариями.