Принципы проектирования (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)
Проверка принципа проектирования
- Изолировать каждый связанный объект через посредника
- Соблюдайте принцип открытого и закрытого
шаблон посетителя
- Разделение операций с данными и структур данных
- Не много сценариев использования
Режим переводчика
- Описать, как грамматика языка определяется, интерпретируется и компилируется
- для профессионального использования
Комплексное приложение
Об интервью
- Может назвать шаблон проектирования, на котором фокусируется курс
ежедневное использование
- Поймите ключевые шаблоны проектирования и заставьте себя подражать и осваивать
- Очень полезный шаблон проектирования, избирательно используемый в соответствии с бизнес-сценариями.