何为设计
即按照哪一种思路或者标准来实现功能
功能相同,可以有不同的设计方案来实现
伴随着需求的增加,设计的作用才能体现出来
设计准则:
1 小既是美
2 让每个程序只做好一件事
3 快速建立原型
4 舍弃高效率而取可移植性
5采用纯文本来存储数据
6 充分利用软件的杠杆效应(软件复用)
7使用shell脚本来提高杠杆效应和可移植性
8 避免强制性的用户界面
9让每一个程序都称为过滤器
小准则:
允许用户定制环境
尽量使操作系统内核小而轻量化
使用小写字母并尽量简短
沉默是金
各部分之和大于整体
寻求90%的解决方案
SOLID五大设计原则
S-单一职责原则(一个程序只做好一件事。 如果功能过于复杂就拆开,每个部分保持独立)
O-开放封闭原则(对扩展开放,对修改封闭 增加需求时,扩展新代码,而非修改已有的代码 这是软件设计的终极目标)
L-李氏置换原则(子类能覆盖父类 父类能出现的地方子类就能出现 js中使用较少(弱类型&继承使用较少))
I-接口独立原则(保持接口的单一独立 避免出现胖接口 js中没有接口(typescript例外)使用较少 类似于单一职责原则 这里更关注接口)
D-依赖导致原则(面向接口编程,依赖于抽象而不是依赖于具体 使用方只关注具体类的实现 js中使用较少(没有接口&弱类型))
设计原则总结
SO 前端体现较多
LID 体现较少
用Promise来说明SO
单一职责原则: 每个then中的逻辑只做好一件事
开放封闭原则: 如果新增加需求,扩展then
function loadImg (src) {
let promise = new Promise(function (resolve, reject) {
let img = document.createElement('img')
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject(new Error('图片加载失败'))
}
img.src = src
this.$refs.div.appendChild(img)
})
return promise
}
let src = 'http://pic44.nipic.com/20140723/18505720_094503373000_2.jpg'
let result = loadImg(src)
result.then(function (img) {
alert(`width:${img.width}`)
return img
}).then(function (img) {
alert(`height:${img.height}`)
return img
}).then(function (img) {
alert(`src:${img.src}`)
}).catch(function (sx) {
alert(sx)
})
23种设计模式
创建型
**工厂模式(工厂方法模式,抽象工厂模式,建造者模式)
单例模式 原型模式**
结构型
适配器模式 装饰器模式 代理模式 外观模式 桥接模式 组合模式 享元模式
行为型
策略模式 迭代器模式 模板方法模式 职责链模式 观察者模式 命令模式 备忘录模式 中介者模式 状态模式 解释器模式 访问者模式
刻意训练
面试题
1 画出UML类图
2 用es6写出来
class Car {
constructor (name, number) {
this.number = number
this.name = name
}
}
// 快车 子类
class Kuaiche extends Car {
constructor (name, number) {
super(name, number)
this.price = 1
}
}
class Chuanche extends Car {
constructor (name, number) {
super(name, number)
this.price = 2
}
}
class Trip {
constructor (car) {
this.car = car
}
start () {
console.log(`行程开始,名称:${this.car.name},车牌号:${this.car.number}`)
}
end () {
console.log('行程结束,金额:' + this.car.price * 5)
}
}
let car = new Chuanche('桑塔纳', 78652)
let tatolTrip = new Trip(car)
tatolTrip.start()
tatolTrip.end()
第二题
UML类图
代码
// 车
class Car {
constructor(num) {
this.num = num
}
}
// 入口摄像头
class Camera {
shot(car) {
return {
num: car.num,
inTime: Date.now()
}
}
}
// 出口显示器
class Screen {
show(car, inTime) {
console.log('车牌号', car.num)
console.log('停车时间', Date.now() - inTime)
}
}
// 停车场
class Park {
constructor(floors) {
this.floors = floors || []
this.camera = new Camera()
this.screen = new Screen()
this.carList = {}
}
in(car) {
// 获取摄像头的信息:号码 时间
const info = this.camera.shot(car)
// 停到某个车位
const i = parseInt(Math.random() * 100 % 100)
const place = this.floors[0].places[i]
place.in()
info.place = place
// 记录信息
this.carList[car.num] = info
}
out(car) {
// 获取信息
const info = this.carList[car.num]
const place = info.place
place.out()
// 显示时间
this.screen.show(car, info.inTime)
// 删除信息存储
delete this.carList[car.num]
}
emptyNum() {
return this.floors.map(floor => {
return `${floor.index} 层还有 ${floor.emptyPlaceNum()} 个车位`
}).join('\n')
}
}
// 层
class Floor {
constructor(index, places) {
this.index = index
this.places = places || []
}
emptyPlaceNum() {
let num = 0
this.places.forEach(p => {
if (p.empty) {
num = num + 1
}
})
return num
}
}
// 车位
class Place {
constructor() {
this.empty = true
}
in() {
this.empty = false
}
out() {
this.empty = true
}
}
// 测试代码------------------------------
// 初始化停车场
const floors = []
for (let i = 0; i < 3; i++) {
const places = []
for (let j = 0; j < 100; j++) {
places[j] = new Place()
}
floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)
// 初始化车辆
const car1 = new Car('A1')
const car2 = new Car('A2')
const car3 = new Car('A3')
console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)
console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三辆车离开')
park.out(car3)
工厂模式
class Product {
constructor (name) {
this.name = name
}
init () {
alert('init')
}
fun1 () {
alert('fun1')
}
fun2 () {
alert('fun2')
}
}
class Creator {
create (name) {
return new Product(name)
}
}
let creator = new Creator()
let p = creator.create('p1')
p.init()
p.fun1()
模拟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)
}
let di = window.$('div')
console.log(di)`
单例模式
class SingleObject {
login () {
console.log('login....')
}
}
SingleObject.getInstance = (function () { // SingleObject的静态方法
let instance
return function () {
if (!instance) {
instance = new SingleObject()
}
return instance
}
})()
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log('obj1 === obj2', obj1 === obj2)
console.log('分割线------------')
let obj3 = new SingleObject()// 无法完全控制
console.log('obj1 === obj3', obj1 === obj3)
单例模式
符合单一职责的原则,只实例化唯一的对象
class LoginForm() {
constructor() {
this.state = 'hide'
}
hide() {
if(this.state === 'hide'){
console.log('已经隐藏')
return
}
this.state == 'hide'
consoel.log('隐藏成功')
}
show() {
if(this.state === 'show'){
console.log('已經顯示')
return
}
this.state === 'show'
console.log('顯示成功')
}
}
LoginForm.instance = (function(){
let instance
return function(){
if(!instance){
instance = new LoginForm()
}
return instance
}
})()
let login1 = LoginForm.instance()
login1.hide()
let login2 = LoginForm.instance()
login2.hide()
适配模式
将旧接口与使用者进行分离
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)
vue中的computed方法也是适配模式
<template>
<div>
<p>{{msg}}</p>
<p>{{reverseMsg}}<p>
<div>
</template>
<script>
export default {
data () {
return {
msg: 'hello'
}
},
computed: {
reverseMsg: function () {
return this.msg.split('').reverse().join('')
}
}
}
<script>
装饰器模式
为对象添加新功能
不改变其原有的结构和功能
将现有对象和装饰器进行分离,两者独立存在
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()
console.log('----------')
let dec = new Decorator(circle)
dec.draw()
装饰类
相当于把类放到它顶上@的那个函数去执行,所有的装饰器都是一个函数
function testDec (dec) {
return function (target) {
target.isDec = dec
}
}
@testDec(false)
class Demo {
}
alert(Demo.isDec)
// 装饰器的原理
@decorator
class A {}
// 等同于
class A {}
A = decorator(A)
function mixins (...list) {
return function (target) {
Object.assign(target.prototype, ...list)
}
}
const Foo = {
foo () {
alert('foo')
}
}
@mixins(Foo)
class Demo {
}
let demo = new Demo()
demo.foo()
修饰方法
让class类的方法只读不可修改
// descriptor属性描述对象(Object.defineProperty中会用到),原来的值如下:
// {
// value:specifiedFunction,//属性的值
// enumerable:false,//是否可枚举
// configurable:true,//是否可配置
// writable:true//是否可更改,写入
// }
function readonly (target, name, descriptor) {
descriptor.writable = false
return descriptor
}
class People {
constructor () {
this.first = 'A'
this.last = 'B'
}
@readonly
name () {
return `${this.first} ${this.last}`
}
}
let people = new People()
console.log(people.name())
people.name = function () {
alert('a')
}
修饰添加的方法
**descriptor.value是要修饰的方法
name是这个方法名**
function log (target, name, descriptor) {
let oldValue = descriptor.value
descriptor.value = function () {
console.log(`calling ${name} width`, arguments)
return oldValue.apply(this, arguments)
}
return descriptor
}
class Math {
@log
add (a, b) {
return a + b
}
}
let p = new Math()
console.log(p.add(2, 4))
core-decorators.js
core-decorators.js是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。
import {readonly} from 'core-decorators'
class People {
@readonly
name () {
return '小小'
}
}
let p = new People()
console.log(p.name())
p.name = function () {
alert('kkk')
}
表示该方法将要废除
import {deprecate} from 'core-decorators'
class People {
@deprecate('即将废除', {url: 'http//www.baidu.com'})
name () {
return '小小'
}
}
let p = new People()
console.log(p.name())
代理模式
**使用者无权访问目标对象
中间加代理,通过代理做授权和控制**
代理类与目标类分离,隔离开目标类和使用者
class ReadImg {
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 ReadImg(fileName)
}
display () {
this.realImg.display()
}
}
let proxyImg = new ProxyImg('png1')
proxyImg.display()
**场景:网页事件代理
jQuery $.proxy
Es6 Proxy**
网页事件代理
<div id="app">
<div id="div1">
<a href="#">a1<a>
<a href="#">a2<a>
<a href="#">a3<a>
<a href="#">a4<a>
<a href="#">a5<a
</div>
</div>
<!-- built files will be auto injected -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
var div = document.getElementById('div1')
div.addEventListener('click',function(e){
let target = e.target
if(target.nodeName == 'A'){
alert(target.innerHTMl)
}
})
<script>
jQuery的 $.proxy代理
<div id="app">
<div id="div1">
<a href="#">a1<a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4<a>
<a href="#">a5</a>
</div>
</div>
<!-- built files will be auto injected -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$('#div1').click(function(){
setTimeout($.proxy(function(){
$(this).css('background-color','yellow')
},this),1000)
})
<script>
Es6的proxy代理
let star = {
age: 25,
phone: 'star:177772177',
name: '小明'
}
let agent = new Proxy(star, {
get: function (target, key) { // target就是代理的对象,key是属性
if (key === 'phone') {
return 'agent 188888888'
} else if (key === 'price') {
return 12000
}
return target[key]
},
set: function (target, key, val) { // val就是重新赋的值
if (key === 'customPrice') {
if (val < 10000) {
throw new Error('太低了')
} else {
target[key] = val
return true
}
}
}
})
console.log(agent.phone)
console.log(agent.name)
console.log(agent.price)
agent.customPrice = 1500
console.log('agent.customPrice', agent.customPrice)
代理模式VS适配器模式
适配器模式:提供一个不同的接口(如不同版本的插头)
代理模式:提供一模一样的接口
代理模式VS装饰器模式
装饰器模式:扩展功能,原有功能不变且可直接使用
代理模式:显示原有的功能,但是经过限制或者阉割之后的
外观模式
为子系统中的一组接口提供了一个高层接口
使用者使用高层接口
不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用
观察者模式
**发布 订阅
一对多**
主题和观察者分离,不是主动触发而是被动监听,两者解耦
// 主题,保存状态,状态改变之后触发所有观察者对象
class Subject {
constructor () {
this.state = 0
this.observers = []
}
getState () {//获取状态
return this.state
}
setState (state) {//设置状态
this.state = state
this.notifyAllObservers()
}
notifyAllObservers () {//触发所有的
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 s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)
s.setState(1)
s.setState(2)
s.setState(3)
当我设置新值,会触发所有观察者的updata方法
网页事件绑定
Promise
function loadImg (src) {
var promise = new Promise(function (resolve, reject) {
var img = document.createElement('img')
document.body.appendChild(img)
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject('图片加载失败')
}
img.src = src
})
return promise
}
let src = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1577934260276&di=4733ee70834444d221274f497173c917&imgtype=0&src=http%3A%2F%2Fdmimg.5054399.com%2Fallimg%2Fpkm%2Fpk%2F22.jpg'
var result = loadImg(src)
result.then(function (img) {
console.log('width', img.width)
return img
}).then(function (img) {
console.log('height', img.height)
})
jQuery callbacks
Nodejs自定义事件
const EventEmitter = require('events').EventEmitter
// 继承
class Dog extends EventEmitter{
constructor(name){
super()
this.name = name
}
}
let simon = new Dog('simon')
simon.on('bark',function(){
console.log(this,function(){
console.log(this.name,'barked')
})
})
setInterval(() => {
simon.emit('bark')
}, 1000)
const fs = require('fs')
const readStream = fs.createReadStream('./data/file1.txt')
let length = 0
readStream.on('data',function(chunk){
let len = chunk.toString().length
console.log('len',len)
length += len
})
readStream.on('end',function(){
console.log('length',length)
})
const fs = require('fs')
const readline = require('readline')
let rl = readline.crateInterface({
input: fs.createReadStream('./data/file.txt')
})
let lineNum = 0;
rl.on('line',function(){
lineNum++
})
rl.on('close',function(){
console.log('lineNum',lineNum)
})
其他场景
**nodejs中:处理http请求;多进程通讯
Vue和React组件生命周期触发
Vue watch**
迭代器模式
**顺序访问一个集合
使用者无需知道集合的内部结构(封装)**
**迭代器对象与目标对象分离
迭代器将使用者与目标对象隔离开**
使用场景
比如说都是数组但是可以使用的方法却不同
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
let arr = [1, 2, 3]
let nodeList = document.getElementsByName('a')
let p = $('a')
arr.forEach(item => {
console.log(item)
});
var i,len = nodeList.length
for(i= 0,i<len; i++){
console.log(nodeList[i])
}
p.each(function(key,elem){
console.log(key,elem)
})
写一个函数封装,迭代器
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
let arr = [1, 2, 3]
let nodeList = document.getElementsByName('a')
let p = $('a')
function each(data){
let $data = $(data)//生成迭代器 生成jquery的对象
$data.each(function(key,val){
console.log(key,val)
})
}
each(arr)
each(nodeList)
each(p)
迭代器模式的特点:
顺序遍历有序集合
使用者不必知道集合的内部结构
</script>
UML类图
class Interator {
constructor (container) {
this.list = container.list
this.index = 0
}
next () {
if (this.hasNext()) {
return this.list[this.index++]
}
return null
}
hasNext () {
if (this.index >= this.list.length) {
return false
}
return true
}
}
class Container {
constructor (list) {
this.list = list
}
getInterator () {
return new Interator(this)
}
}
let arr = [1, 2, 3, 4]
let container = new Container(arr)
let interator = container.getInterator()
while (interator.hasNext()) {
console.log(interator.next())
}
**场景
jQuery each
ES6 Iterator**
ES6 Iterator为何存在?
Es6语法中,有序集合的数据类型已经有很多
Array Map Set String TypedArray arguments NodeList
需要有一个统一的遍历接口来遍历所有数据类型
注意,object不是有序集合,可以用Map代替
ES6 Interator是什么?
**以上数据类型,都有【Symbol.interator】属性/属性key
属性值是函数,执行函数返回一个迭代器**
**这个迭代器就有next方法可顺序迭代子元素
可运行Array.prototype[Symbol.interator]来测试**
function each (data) {
let iterator = data[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
}
let arr = [1, 2, 3]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 100)
each(arr)
each(nodeList)
each(m)
Symbol.iterator并不是人人都知道
也不是每个人都需要封装一个each方法
因此有了for..of语法 语法糖
优化
function each (data) {
let iterator = data[Symbol.iterator]()
let item = {done: false}
while (!item.done) {
item = iterator.next()
if (!item.done) {
console.log(item.value)
}
}
}
let arr = [1, 2, 3]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 100)
each(arr)
each(nodeList)
each(m)
继续优化
function each (data) {
// 带有遍历器特性的对象:data[Symbol.iterator]有值
for (let item of data) {
console.log(item)
}
}
let arr = [1, 2, 3]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 100)
each(arr)
each(nodeList)
each(m)
ES6 Interator与Generator
Interator的价值不限于上述几个类型的遍历
还有generator函数的使用
即只要返回的数据符合Iterator接口的要求
即可使用Iterator语法,这就是迭代器模式
function* foo () {
yield '1'
yield '2'
return '6'
}
for (let item of foo()) {
console.log(item)
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。