前端基础之设计模式篇
设计模式篇
1.简单工厂模式:简单工厂模式就是将创建过程单独封装起来。在前端常用这种模式来创建多个相同但互不相关的实例或者通过闭包来创立一个与外界隔离的环境,示例如下:
// 出处:https://cn.vuejs.org/v2/guide/render-function.html#VNode-%E5%BF%85%E9%A1%BB%E5%94%AF%E4%B8%80
// 创建多个相同但互不相关的实例
render: function (createElement) {
return createElement('div',
Array.apply(null, { length: 20 }).map(function () {
return createElement('p', 'hi')
})
)
}
// 出处:vue.js 源码 state.js
// 通过闭包来创立一个与外界隔离的环境(把key与外界隔离)
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
2.抽象工厂模式:抽象工厂模式也是将创建过程进行封装,但是传入的参数一个比较抽象的东西(比如说传入一个函数)。在前端常用这种模式来封装抽象的东西。示例如下:
// 出处:vue.js 源码 util.js
// 封装抽象的东西 fn
// 本来获取缓存的过程是 cache(id) = result,但是 result = fn(middle) ,所以 cache(id) = fn(middle)
// 一般来说我们是给 cache 函数创建一个工厂函数,但是这里我们把 fn 当做参数来创建一个工厂函数,最终提供 id 到 middle 的映射。
export function cached<F: Function> (fn: F): F {
const cache = Object.create(null)
return (function cachedFn (str: string) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}: any)
}
// 使用上面的 cached 方法
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
// typescript 的泛型
function identity<T>(arg: T): T {
return arg;
}
3.单例模式:保证系统中只有一个对象的实例。在前端常用这种模式来保证一个类不会被多次实例化。这种模式的实现方法有很多,最常见的有两种,第一个是直接给予警告并中断,第二个是建立缓存从缓存里面取。示例如下:
// 出处:vuex 源码 store.js
// 直接给予警告并中断
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
// vue 源码 use.js
// 建立缓存从缓存里面取
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
4.原型模式:使用克隆原型的方式来实现对象的拷贝。在前端常用这种模式来实现原型继承。示例如下:
// 原型继承
ObjectB.prototype = new ObjectA();
5.装饰器模式:是指对已有功能做个扩展,但是只关心扩展出来的那部分新功能。在前端 ES7 里面已经实现了装饰器语法糖@。示例如下:
// 装饰器函数,它的第一个参数是目标类
function classDecorator(target) {
target.hasDecorator = true
return target
}
// 将装饰器“安装”到Button类上
@classDecorator
class Button {
// Button类的相关逻辑
}
6.适配器模式:通过把一个类的接口变换成另一种接口,来帮助我们解决不兼容的问题。在前端常用这种模式来做适配。示例如下:
// 出处:vue 源码 patch.js
// vue 把 web 和 weex 两种平台的操作统一封装到 modules 和 nodeOps 里面
const { modules, nodeOps } = backend
7.代理模式:就是我们通过一个第三者(代理)来间接达到访问的目的。在前端我们用这种模式来代理方法或属性。实现方法有三种:第一种是把原方法储存起来,然后改写原方法;第二种是使用 Object.defineProperty;第三种是使用proxy。示例如下:
// 代理 fetch
const fetch_helper = {
originalFetch: window.fetch.bind(window),
myFetch: function (...args) {
return fetch_helper.originalFetch(...args).then((response) => {
// do something
return response;
});
},
}
// vue 源码 state.js
// 把 props 代理到 vm 上面去
for (const key in propsOptions) {
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
8.策略-状态模式:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。在前端常用这种模式来添加各种钩子函数。实例如下:
// vue 源码 patch.js
// 把多个钩子作为状态封装到对象中
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
9.观察者模式:定义了一种一对多的依赖关系,让多个观察者同时监听某一个目标对象,当这个目标对象的状态发生变化,会通知所有观察者更新。前端常用这种模式来实现各种事件绑定。示例略。
10.订阅者-发布者模式:使用了订阅中心来管理订阅和发布的观察者模式,在这个模式中,订阅者和发布者只能通过订阅中心来进行交互。这种模式的好处是解耦了订阅者和发布者。前端常用这种模式来实现各种依赖和更新,比如 Vue 的数据劫持。