何爲設計
即按照哪種思路或者標準來實現功能
功能相同,能夠有不一樣的設計方案來實現
伴隨着需求的增長,設計的做用才能體現出來
設計準則:
1 小既是美
2 讓每一個程序只作好一件事
3 快速創建原型
4 捨棄高效率而取可移植性
5採用純文原本存儲數據
6 充分利用軟件的槓桿效應(軟件複用)
7使用shell腳原本提升槓桿效應和可移植性
8 避免強制性的用戶界面
9讓每個程序都稱爲過濾器
小準則:
容許用戶定製環境
儘可能使操做系統內核小而輕量化
使用小寫字母並儘可能簡短
沉默是金
各部分之和大於總體
尋求90%的解決方案css
SOLID五大設計原則
S-單一職責原則(一個程序只作好一件事。 若是功能過於複雜就拆開,每一個部分保持獨立)html
O-開放封閉原則(對擴展開放,對修改封閉 增長需求時,擴展新代碼,而非修改已有的代碼 這是軟件設計的終極目標)前端
L-李氏置換原則(子類能覆蓋父類 父類能出現的地方子類就能出現 js中使用較少(弱類型&繼承使用較少))vue
I-接口獨立原則(保持接口的單一獨立 避免出現胖接口 js中沒有接口(typescript例外)使用較少 相似於單一職責原則 這裏更關注接口)node
D-依賴致使原則(面向接口編程,依賴於抽象而不是依賴於具體 使用方只關注具體類的實現 js中使用較少(沒有接口&弱類型))jquery
設計原則總結
SO 前端體現較多
LID 體現較少git
用Promise來講明SO
單一職責原則: 每一個then中的邏輯只作好一件事
開放封閉原則: 若是新增長需求,擴展thenes6
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) })
**工廠模式(工廠方法模式,抽象工廠模式,建造者模式)
單例模式 原型模式**github
適配器模式 裝飾器模式 代理模式 外觀模式 橋接模式 組合模式 享元模式面試
策略模式 迭代器模式 模板方法模式 職責鏈模式 觀察者模式 命令模式 備忘錄模式 中介者模式 狀態模式 解釋器模式 訪問者模式
面試題
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()
`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是一個第三方模塊,提供了幾個常見的修飾器,經過它能夠更好地理解修飾器。
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>
<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>
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)
適配器模式:提供一個不一樣的接口(如不一樣版本的插頭)
代理模式:提供如出一轍的接口
裝飾器模式:擴展功能,原有功能不變且可直接使用
代理模式:顯示原有的功能,可是通過限制或者閹割以後的
爲子系統中的一組接口提供了一個高層接口
使用者使用高層接口
不符合單一職責原則和開放封閉原則,所以謹慎使用,不可濫用
**發佈 訂閱
一對多**
主題和觀察者分離,不是主動觸發而是被動監聽,二者解耦
// 主題,保存狀態,狀態改變以後觸發全部觀察者對象 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方法
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) })
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語法中,有序集合的數據類型已經有不少
Array Map Set String TypedArray arguments NodeList
須要有一個統一的遍歷接口來遍歷全部數據類型
注意,object不是有序集合,能夠用Map代替
**以上數據類型,都有【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)
優化
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)
Interator的價值不限於上述幾個類型的遍歷
還有generator函數的使用
即只要返回的數據符合Iterator接口的要求
便可使用Iterator語法,這就是迭代器模式
function* foo () { yield '1' yield '2' return '6' } for (let item of foo()) { console.log(item) }