SOLID设计原则
- s: 单一原则:一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类
- o: 开放封闭原则:对外扩展是开放的,对于修改是封闭的
- l: 里氏置换原则:当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
- i: 接口分离原则:使用多个专门的接口比使用单一的总接口总要好
- d: 依赖倒置原则: 抽象不应该依赖于细节,细节应该依赖于抽象
单例模式
单例就是保证一个类只有一个实例,实现方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回
应用场景
在Jquery中
jquery中只会创建一个$对象,会先去判断$对象是否存在,如果不存在创建一个新的对象
if(window.jQuery!=null){
return window.jQuery;
}else{
//init~~~~~~~
}
模态窗口
一般网站都会有用户系统,登录窗口或者toast提示,会去创建一个getInstance的静态方法来统一调用
工厂模式
工厂模式主要出现在面向对象创建实例的过程中,其本质是为了更方便生成实例,构造函数和工厂方法分离。
应用场景
React.createElement
class Vnode(tag, attrs, children){
……
}
React.createElement = function(tag, attrs, children) {
return new Vnode(tag, attrs, children)
}
jquery
当我们在使用jquery的时候,直接返回的是一个jquery实例,而不是需要new Jquery(′div′)才能够返回
class JQuery{
construtor(selector){
……
}
……
}
window.$ = function(selector) {
return new JQuery(selector);
}
函数缓存(备忘模式)
高阶函数
高阶函数就是那种输入参数里面有一个或者多个函数,输出也是函数的函数,这个在js里面主要是利用闭包实现的,最简单的就是经常看到的在一个函数内部输出另一个函数,每次执行都会缓存方法执行的结果
适配器模式
适配器模式是一对相对简单的模式。但适配器模式在JS中的使用场景很多,在参数的适配上,有许多库和框架都使用适配器模式;数据的适配在解决前后端数据依赖上十分重要。我们要认识到的是适配器模式本质上是一个”亡羊补牢”的模式,它解决的是现存的两个接口之间不兼容的问题,你不应该在软件的初期开发阶段就使用该模式;如果在设计之初我们就能够统筹的规划好接口的一致性,那么适配器就应该尽量减少使用。
适配器模式不会增加或者减少接口的功能,提供一个不同的接口
应用场景
React-Redux
redux为了和react适配,所有有mapStateToProps()这个函数来吧state转为Props外部状态,这样就可以从外部又回到组件内了。你看这个就是适配器模式(Adaptor)
观察者模式
- 发布 && 订阅
- 一对N(一对一,一对多)
Promise实现
function Promise(fn) {
// 加入状态
var state = 'pending';
var value = null;
//callbacks为订阅数组,因为可能同时有很多个回调
var callbacks = [];
//加入订阅数组
this.then = function (onFulfilled) {
if(state==='fullfilled'){
callbacks.push(onFulfilled);
//支持链式调用
return this;
}
};
//遍历订阅回调数组,执行回调
function resolve(value) {
// 加入setTimeout,保证先注册then,然后在执行回调
setTimeout(function() {
state = 'fulfilled';
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)
}
fn(resolve);
}
参考
Events模块
node.js中有一个底层的Events模块,被其他的模块大量使用,可以说是node非常核心的一个模块了.其本质还是一个观察者模式
const eventEmitter = require('events').EventEmitter
const emitter1 = new eventEmitter()
emitter1.on('some', info=> {
console.log('fn1', info)
})
emitter1.on('some', info=> {
console.log('fn2', info)
})
emitter1.emit('some', 'xxxx')
vue中的watch函数
vue有个订阅数据中心,有订阅队列、发布方法、还有通知方法
生命周期
还有vue与react中的生命周期钩子,下面是vue源码,在new Vue的时候使用callHook方法执行我们定义的方法
迭代器模式
迭代器模式就是为了简化有序集合的遍历产生的模式,统一遍历的方式,在ES6中通过Symbol.iterator标识数据是可遍历的
function each(data, callback) {
//通过js提供的Symbol.iterator返回迭代器
const iterator = data[Symbol.iterator]()
let item = {done: false}
while(!item.done) {
item.iterator.next()
if(!item.done) {
callback(item)
}
}
}
let log = (data)=>{
console.log(data)
}
let arr = [1,2,3,4,5,6]
let map = new Map()
map.set('a', 100)
map.set('b', 200)
each(arr)
each(map)
拥有迭代器模式的数据,可以通过for……of语法糖来遍历
for(let item of data) {
cb(item)
}
装饰器模式
装饰器它能够动态为一个函数、方法或者类添加新的行为,而不需要通过子类继承或直接修改函数的代码来获取新的行为能力
- 为对象添加新的功能(扩展功能)
- 不改变原有的结构和逻辑
应用场景
简单案例
@test
class Demo{
}
function test(target){
target.isDec=true
}
alert(Demo.isDec)
@test(true)
class Demo{
}
function test(isDec){
return function(target) {
target.isDec=isDec
}
}
alert(Demo.isDec)
函数柯里化
const curry = func => (...rest) => {
// coding here
var length = func.length;
let arr = [].concat(rest)
let temp = function (..._rest) {
arr = arr.concat(_rest)
if (arr.length === length) {
func(...arr)
}
return temp
}
return temp
};
const fn = curry(function (x, y, z) {
console.log(x, y, z);
});
// 保证以下方式皆可调用成功,能够打印一次性出 4 次 1 2 3
fn(1, 2, 3);
fn(1, 2)(3);
fn(1)(2)(3);
fn(1)(2, 3);
React-Redux
class TodoListContainer extends React.Component {
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoListContainer)
// 源码实现
connectHOC = connectAdvanced;
mergePropsFactories = defaultMergePropsFactories;
selectorFactory = defaultSelectorFactory;
function connect (
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual, // 严格比较是否相等
areOwnPropsEqual = shallowEqual, // 浅比较
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
renderCountProp, // 传递给内部组件的props键,表示render方法调用次数
// props/context 获取store的键
storeKey = 'store',
...extraOptions
} = {}
) {
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
// 调用connectHOC方法
connectHOC(selectorFactory, {
// 如果mapStateToProps为false,则不监听store state
shouldHandleStateChanges: Boolean(mapStateToProps),
// 传递给selectorFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
renderCountProp, // 传递给内部组件的props键,表示render方法调用次数
// props/context 获取store的键
storeKey = 'store',
...extraOptions // 其他配置项
});
}
function connectAdvanced (
selectorFactory,
{
renderCountProp = undefined, // 传递给内部组件的props键,表示render方法调用次数
// props/context 获取store的键
storeKey = 'store',
...connectOptions
} = {}
) {
// 获取发布订阅器的键
const subscriptionKey = storeKey + 'Subscription';
const contextTypes = {
[storeKey]: storeShape,
[subscriptionKey]: subscriptionShape,
};
const childContextTypes = {
[subscriptionKey]: subscriptionShape,
};
return function wrapWithConnect (WrappedComponent) {
const selectorFactoryOptions = {
// 如果mapStateToProps为false,则不监听store state
shouldHandleStateChanges: Boolean(mapStateToProps),
// 传递给selectorFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
...connectOptions,
...others
renderCountProp, // render调用次数
shouldHandleStateChanges, // 是否监听store state变更
storeKey,
WrappedComponent
}
// 返回拓展过props属性的Connect组件
return hoistStatics(Connect, WrappedComponent)
}
}
属性只读
只能执行,不能修改,使用到了Object.defineProperty() 的 属性描述descriptor 的 wirtebale 属性
class Person{
constructor(){
this.first = "zhangsan0"
this.last = "3"
}
@readonly
name(){
return `${this.first}${this.last}`
}
}
function readonly(target,name,descriptor){
descriptor.writebale = false
return descriptor
}
日志打印
class Math {
@log
add(a,b){
return a+b
}
}
function log(target,name,descriptor) {
let oldValue = descriptor.value
descriptor.value = function() {
console.log('日志打印')
return oldValue.apply(this,arguments)
}
return descriptor
}
const math = Math();
//console.log('日志打印')
const result = math.add(100,33);
在日常开发中,可用其用来进行日志打印,埋点上报
代理模式
代理模式不愿意或者不想对原对象进行直接操作,我们使用代理就是让它帮原对象进行一系列的操作,代理和本体接口保持一致性,提供一模一样的接口
场景
事件代理
<div class="container">
<a>1</a>
<a>2</a>
<a>3</a>
<a>4</a>
<a>5</a>
</div>
window.onload = function () {
document.getElementsByClassName('container')[0].addEventListener('click', function (e) {
if (e.target.nodeName === "A") {
alert(e.target.innerHTML)
}
})
}
ES6 Proxy
const list = [1, 2];
const observer = new Proxy(list, {
get: function(target,key){
// 代理处理,如果获取的是元素的第二个元素
if(key==1){
return 3;
}
},
set: function(target, key, value, receiver) {
console.log(`prop: ${value} is changed!`);
return Reflect.set(...arguments);
},
});
observer[3] = 4;
console.log(observer[1])
外观模式
它为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,使用者只需要直接与外观角色交互,使用者与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度
场景
批量设置style
将多个样式设置批量设置
function setStyles( id, styles ){
var element = document.getElementById( id );
for( var key in styles ){
if( styles.hasOwnProperty( key ) ){
element.style[ key ] = styles[ key ];
}
}
}
setStyles( 'content', {
color : 'red',
height : '200px'
} );
组件库设计
在组件目录中,统一有个出口 index.tsx 包括了各个子组件的引用,统一 export