前言
由于很多小伙伴看了 《普通人如何进大厂》这篇文章对我的笔记比较感兴趣,所以我花时间整理提炼了一下跟大家分享,不保证内容完全正确,仅供参考哈
本文内容来自修言的《JavaScript 设计模式核⼼原理与应⽤实践》掘金小册子的核心总结,侵权删。
其他笔记传送门
学习目的:提高驾驭技术的能力
具体来说,它分为以下三个层次:
- 能用健壮的代码去解决具体的问题
- 能用抽象的思维去应对复杂的系统
- 能用工程化的思想去规划更大规模的业务
工厂模式
简单工厂- 处理变与不变的
工厂模式:将创建对象的过程单独封装,实现无脑传参,核心:处理变与不变的
改进前:
function Coder(name , age) {
this.name = name
this.age = age
this.career = 'coder'
this.work = ['写代码','写系分', '修Bug']
}
function ProductManager(name, age) {
this.name = name
this.age = age
this.career = 'product manager'
this.work = ['订会议室', '写PRD', '催更']
}
function Factory(name, age, career) {
switch(career) {
case 'coder':
return new Coder(name, age)
break
case 'product manager':
return new ProductManager(name, age)
break
...
}
改进后
function User(name,age,career,word){
this.name = name
this.age = age
this.career = career
this.word = word
}
function Factory(name,age,career){
let word=[]
switch(career){
case 'coder':
word=['写代码','写系分', '修Bug']
break;
case 'product manager':
word=['写原型','催进度']
break;
case 'boss':
word=['喝茶','见客户','谈合作']
break;
}
return new User(name,age,career,word)
}
抽象工厂- 开放封闭原则
简单工厂因为没有遵守开放封闭原则, 暴露一个很大的缺陷。例如若我们添加管理层一些考评权限,难道我们要重新去修改Factory函数吗?这样做会导致Factory会变得异常庞大,而且很容易出bug,最后非常难维护
class MobilePhoneFactory {
// 提供操作系统的接口
createOS(){
throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
}
// 提供硬件的接口
createHardWare(){
throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!");
}
}
// 具体工厂继承自抽象工厂
class FakeStarFactory extends MobilePhoneFactory {
createOS() {
// 提供安卓系统实例
return new AndroidOS()
}
createHardWare() {
// 提供高通硬件实例
return new QualcommHardWare()
}
}
// 定义操作系统这类产品的抽象产品类
class OS {
controlHardWare() {
throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
}
}
// 定义具体操作系统的具体产品类
class AndroidOS extends OS {
controlHardWare() {
console.log('我会用安卓的方式去操作硬件')
}
}
class AppleOS extends OS {
controlHardWare() {
console.log('我会用🍎的方式去操作硬件')
}
}
// 定义手机硬件这类产品的抽象产品类
class HardWare {
// 手机硬件的共性方法,这里提取了“根据命令运转”这个共性
operateByOrder() {
throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
}
}
// 定义具体硬件的具体产品类
class QualcommHardWare extends HardWare {
operateByOrder() {
console.log('我会用高通的方式去运转')
}
}
class MiWare extends HardWare {
operateByOrder() {
console.log('我会用小米的方式去运转')
}
}
// 这是我的手机
const myPhone = new FakeStarFactory()
// 让它拥有操作系统
const myOS = myPhone.createOS()
// 让它拥有硬件
const myHardWare = myPhone.createHardWare()
// 启动操作系统(输出‘我会用安卓的方式去操作硬件’)
myOS.controlHardWare()
// 唤醒硬件(输出‘我会用高通的方式去运转’)
myHardWare.operateByOrder()
对原有的系统不会造成任何潜在影响所谓的“对拓展开放,对修改封闭”
单例模式 - 保证一个类只有一个实例
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
class SingleDog {
show(){
console.log('单例方法')
}
}
let single1=new SingleDog()
let single2=new SingleDog()
single1===single2 // false
单例模式要求不管我们尝试去创建多少次,它都只给你返回第一次所创建的那唯一的一个实例。
class SingleDog {
show(){
console.log('单例方法')
}
static getInstance(){
if(!SingleDog.instance){
SingleDog.instance = new SingleDog()
}
return SingleDog.instance
}
}
let single1 = SingleDog.getInstance()
let single2 = SingleDog.getInstance()
single1===single2 // true
单例实际应用 vuex
Store 存放共享数据的唯一数据源,要求一个 Vue 实例只能对应一个 Store,即Vue 实例(即一个 Vue 应用)只会被 install 一次 Vuex 插件
let Vue // 这个Vue的作用和楼上的instance作用一样
...
export function install (_Vue) {
// 判断传入的Vue实例对象是否已经被install过Vuex插件(是否有了唯一的state)
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
// 若没有,则为这个Vue实例对象install一个唯一的Vuex
Vue = _Vue
// 将Vuex的初始化逻辑写进Vue的钩子函数里
applyMixin(Vue)
}
装饰器模式 - 实现只添加不修改
拓展弹窗功能
// 定义打开按钮
class OpenButton {
// 点击后展示弹框(旧逻辑)
onClick() {
const modal = new Modal()
modal.style.display = 'block'
}
}
// 定义按钮对应的装饰器
class Decorator {
// 将按钮实例传入
constructor(open_button) {
this.open_button = open_button
}
onClick() {
this.open_button.onClick()
// “包装”了一层新逻辑
this.changeButtonStatus()
}
changeButtonStatus() {
this.changeButtonText()
this.disableButton()
}
disableButton() {
const btn = document.getElementById('open')
btn.setAttribute("disabled", true)
}
changeButtonText() {
const btn = document.getElementById('open')
btn.innerText = '快去登录'
}
}
const openButton = new OpenButton()
const decorator = new Decorator(openButton)
document.getElementById('open').addEventListener('click', function() {
// openButton.onClick()
// 此处可以分别尝试两个实例的onClick方法,验证装饰器是否生效
decorator.onClick()
})
es7 装饰器
function classDecorator(target) {
target.hasDecorator = true
return target
}
// 将装饰器“安装”到Button类上
@classDecorator
class Button {
// Button类的相关逻辑
}
装饰器的原型
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
装饰器只能用于类或者类的方法原因:普通函数声明会提升
实现 react 的高阶函数
定义装饰器
import React, { Component } from 'react'
const BorderHoc = WrappedComponent => class extends Component {
render() {
return <div style={{ border: 'solid 1px red' }}>
<WrappedComponent />
</div>
}
}
export default borderHoc
应用装饰器
import React, { Component } from 'react'
import BorderHoc from './BorderHoc'
// 用BorderHoc装饰目标组件
@BorderHoc
class TargetComponent extends React.Component {
render() {
// 目标组件具体的业务逻辑
}
}
// export出去的其实是一个被包裹后的组件
export default TargetComponent
适配器模式 - 兼容就是一把梭
原 ajax 定义
function Ajax(type, url, data, success, failed){
// 创建ajax对象
var xhr = null;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
...(此处省略一系列的业务逻辑细节)
var type = type.toUpperCase();
// 识别请求类型
if(type == 'GET'){
if(data){
xhr.open('GET', url + '?' + data, true); //如果有数据就拼接
}
// 发送get请求
xhr.send();
} else if(type == 'POST'){
xhr.open('POST', url, true);
// 如果需要像 html 表单那样 POST 数据,使用 setRequestHeader() 来添加 http 头。
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 发送post请求
xhr.send(data);
}
// 处理返回数据
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
success(xhr.responseText);
} else {
if(failed){
failed(xhr.status);
}
}
}
}
}
fetch 请求封装
export default class HttpUtils {
// get方法
static get(url) {
return new Promise((resolve, reject) => {
// 调用fetch
fetch(url)
.then(response => response.json())
.then(result => {
resolve(result)
})
.catch(error => {
reject(error)
})
})
}
// post方法,data以object形式传入
static post(url, data) {
return new Promise((resolve, reject) => {
// 调用fetch
fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
// 将object类型的数据格式化为合法的body参数
body: this.changeData(data)
})
.then(response => response.json())
.then(result => {
resolve(result)
})
.catch(error => {
reject(error)
})
})
}
// body请求体的格式化方法
static changeData(obj) {
var prop,
str = ''
var i = 0
for (prop in obj) {
if (!prop) {
return
}
if (i == 0) {
str += prop + '=' + obj[prop]
} else {
str += '&' + prop + '=' + obj[prop]
}
i++
}
return str
}
}
fetch 兼容 ajax(放弃ajax)
// Ajax适配器函数,入参与旧接口保持一致
async function AjaxAdapter(type, url, data, success, failed) {
const type = type.toUpperCase()
let result
try {
// 实际的请求全部由新接口发起
if(type === 'GET') {
result = await HttpUtils.get(url) || {}
} else if(type === 'POST') {
result = await HttpUtils.post(url, data) || {}
}
// 假设请求成功对应的状态码是1
result.statusCode === 1 && success ? success(result) : failed(result.statusCode)
} catch(error) {
// 捕捉网络错误
if(failed){
failed(error.statusCode);
}
}
}
// 用适配器适配旧的Ajax方法
async function Ajax(type, url, data, success, failed) {
await AjaxAdapter(type, url, data, success, failed)
}
代理模式
事件代理:点击子元素,用父元素代理
缓存代理
const addAll = function() {
console.log('进行了一次新计算')
let result = 0
const len = arguments.length
for(let i = 0; i < len; i++) {
result += arguments[i]
}
return result
}
// 为求和方法创建代理
const proxyAddAll = (function(){
// 求和结果的缓存池
const resultCache = {}
return function() {
// 将入参转化为一个唯一的入参字符串
const args = Array.prototype.join.call(arguments, ',')
// 检查本次入参是否有对应的计算结果
if(args in resultCache) {
// 如果有,则返回缓存池里现成的结果
return resultCache[args]
}
return resultCache[args] = addAll(...arguments)
}
})()
策略模式 - 消除 if-else 能手
// 定义一个询价处理器对象
const priceProcessor = {
pre(originPrice) {
if (originPrice >= 100) {
return originPrice - 20;
}
return originPrice * 0.9;
},
onSale(originPrice) {
if (originPrice >= 100) {
return originPrice - 30;
}
return originPrice * 0.8;
},
back(originPrice) {
if (originPrice >= 200) {
return originPrice - 50;
}
return originPrice;
},
fresh(originPrice) {
return originPrice * 0.5;
},
};
// 询价函数
function askPrice(tag, originPrice) {
return priceProcessor[tag](originPrice)
}
观察者模式
应用例子:需求发布
流程产品经理开群然后拉人(开发)进群,需求更新时通知开发者,开发者接到到需求开始工作。
// 发布者
class Publisher{
constructor(){
this.observers=[]
}
add(observer){
this.observers.push(observer)
}
remove(observer){
const index =this.observers.findIndex(item=>item===observer)
this.observers.splice(index,1)
}
notify(state){
this.observers.forEach(observer=>observer.update(state))
}
}
// 观察者
class Observer{
constructor() {
console.log('Observer created')
}
update(){
console.log('我干活辣')
}
}
// 产品经理类 (文档发布者)
class Prdpublisher extends Publisher{
constructor(){
super()
this.prdState = {}
this.observers=[]
console.log('Prdpublisher created')
}
getState(){
return this.prdState
}
setState(state){
console.log('this.observers',this.observers)
this.prdState = state
this.notify(state)
}
}
// 开发者类
class DeveloperObserver extends Observer{
constructor(){
super()
this.prdState={}
console.log('DeveloperObserver created')
}
update(state){
this.prdState = state
this.word()
}
word(){
const prdState = this.prdState
console.log('start wording',prdState)
}
}
const observeA = new DeveloperObserver() //前端
const observeB = new DeveloperObserver() //后端
const lilei = new Prdpublisher() // 产品经理
lilei.add(observeA) // 拉群
lilei.add(observeB)
let prd={
// 需求内容
'login':3,
'auth':2
}
// 更新需求 同时通知开发者
lilei.setState(prd)
vue 响应试原理-观察者模式
观察者模式和发布-订阅模式的区别是:
发布-订阅模式,事件的注册和触发发生在独立于双方的第三方平台。观察者模式:发布者会直接触及到订阅者
function observe(target){
if(target && typeof target==='object'){
Object.keys(target).forEach(key=>{
defineReactive(target,key,target[key])
})
}
}
function defineReactive(target, key,val) {
observe(val)
let dep = new Dep()
Object.defineProperty(target, key, {
enumerable:true,
configurable:false,
get() {
return val
},
set(value) {
val=value
dep.notify()
},
});
}
class Dep {
constructor(dep) {
this.deps = [];
}
add(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach((dep) => dep.update());
}
}
vue eventBus
class EventEmitter {
constructor() {
this.handlers = {};
}
on(eventName, cb) {
if (!this.handlers[eventName]) {
this.handlers[eventName] = [cb];
} else {
this.handlers[eventName].push(cb);
}
}
emit(eventName, data) {
if (!this.handlers[eventName]) {
console.log('监听器不存在');
return;
}
const events = this.handlers[eventName].slice();
events.forEach((cb) => {
cb(data);
});
}
off(eventName, cb) {
if (!this.handlers[eventName]) {
console.log('监听器不存在');
return;
}
const callBacks = this.handlers[eventName];
const index = callBacks.findIndex((item) => item === cb);
callBacks.splice(index, 1);
}
once(eventName, cb) {
const wrap = (data) => {
let fn = this.handlers[eventName];
cb(data);
this.off(eventName, fn);
};
this.on(eventName, wrap);
}
}
let eventBus = new EventEmitter();
eventBus.once('success', (data) => {
console.log('data', data);
});
eventBus.emit('success', 456);
eventBus.emit('success', 577);