<返回更多

学好JavaScript设计模式,一文足矣!

2020-05-17    
加入收藏

相信大家在平时的开发中或多或少听说的听说过各种设计模式,有非常常见的,比如:观察者模式,工厂模式,装饰器模式等等,熟练掌握设计模式不仅仅对我们技术提升有巨大的帮助,而且更有利于编程思维的提升,而且如果我们能够熟练的将各种设计模式应用到实际的开发中,对代码的易读和维护都能够起到积极的作用。

学好JavaScript设计模式,一文足矣!

 

设计原则

Tips:在学习设计模式的时候,尽量分开去学,即先学习设计,然后再去学模式,这样的话,对于之后的理解更加容易一些,什么设计呢,其实就是指设计原则,而模式这里指的就是就是我们要讲的设计模式。现在共有5大设计原则,不管是哪种设计模式,都是遵循设计原则的。下面来分别介绍这5大设计原则。

1.单一职责原则

单一职责原则原则就是每个程序只负责做好一件事情,如果功能过于复杂就拆分开,每个部分保持独立。这个其实也符合我们当下流行框架Vue和React的组件化开发,把一个复杂的页面拆分成一些零散的组件,并且每个组件保持独立,同时也可在不同的页面当中实现复用。

2.开放封闭原则

开发封闭原则大白话的意思就是对扩展开放,对修改封闭。放到实际开发中如何去理解呢,我们日常的网站App开发每周都有发不同的版本来增加需求,那么增加需求的时候,尽量要做到扩展新代码,而非修改已有代码,如果我们修改已有代码无疑增加了风险,因为本来原来的代码是没有问题的,加了新的代码之后必然会增加不可预知的风险,当然有的个别需求必须修改已有代码,这个另说。同时这个原则也是我们软件设计的终极目标。

3.李氏置换原则

子类能够覆盖父类,父类能出现的地方,子类就可以出现,这个原则其实在JAVA等语言当中是较为常见的,多用于继承,而JavaScript作为弱类型语言,继承使用其实是很少的,这里简单提一下。

4.接口独立原则

接口独立原则的含义是保持接口的单一独立,避免出现胖接口,JavaScript中是没有接口(typescript例外),使用较少, 它是有点类似于单一职责原则,这里更关注接口。

5.依赖倒置原则

依赖倒置原则的含义是面向接口编程,依赖于抽象而不依赖于具体,使用方只关注接口而不关注具体类的实现,同样这里也是JavaScript中使用较少(没有接口&弱类型)

设计模式

设计模式分类

  1. 创建型

工厂模式(工厂方法模式、抽象工厂模式、建造者模式) 单例模式

  1. 组合型
  2. 适配器模式 装饰器模式 代理模式 外观模式
  3. 行为型
  4. 观察者模式 状态模式

下面我们就来一一结合开发当中的实例,来分析各个设计模式

工厂模式

概念

工厂模式是由一个方法来确定是要创建哪个类的实例,在前端当中最为常见的工厂模式就是new操作的单独封装,当遇到new操作的时候,就要考虑是否该使用工厂模式。这里也可以结合生活中的例子去思考。当你去购买汉堡,直接点餐取餐,不会自己亲手做,商店要“封装”做汉堡的工作,做好直接给买者。也就是说通过提供原材料,最终得到是汉堡还是炸鸡,是由你自己决定的。

前端中实例

1. jQuery当中的$('')

jQuery当中的$('div'),这里的$选择器就是已经封装好的API,这里我们直接使用即可。下面简单实现一个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) {
	
	}
	html(data) {
	
	}
	//等等API
}
window.$ = function (selector) {
	return new jQuery(selector)
}

2. Vue异步组件

这个大家应该比较熟悉,而且官方文档讲的也非常详细,这里直接饮用官方文档的案例,在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:

Vue.component('async-example', function (resolve, reject) {
 setTimeout(function () {
 // 向 `resolve` 回调传递组件定义
 resolve({
 template: '<div>I am async!</div>'
 })
 }, 1000)
})

如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
 // 这个特殊的 `require` 语法将会告诉 webpack
 // 自动将你的构建代码切割成多个包,这些包
 // 会通过 Ajax 请求加载
 require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

Vue.component(
 'async-webpack-example',
 // 这个 `import` 函数会返回一个 `Promise` 对象。
 () => import('./my-async-component')
)

当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:

new Vue({
 // ...
 components: {
 'my-component': () => import('./my-async-component')
 }
})

单例模式

概念

单例模式符合单一职责原则,用大白话描述单例模式就是系统中被唯一使用,例如:电子商务网站常见的购物车和登录都是单例模式的运用。

前端中的实例

1.jQuery中的$('')

仍旧是jQuery当中的$(' ')选择器,整个jQuery框架当中有一个这样的选择器。

2.Redux和Vuex

不管是Redux还是Vuex,里面的状态store都是唯一的,Redux中的store只能通过Reducer去修改,而Vuex中的store只能通过Mutation修改,其余修改方式都是错误的。

适配器模式

概念

适配器模式的含义是旧接口格式和使用者不兼容,中间加一个适配器接口。生活当中随处可见符合适配器模式的例子,如:插头转换器,电脑接口转换器。

前端中的实例

封装旧的接口

这里我来列举一个例子,这里那我们常用的发起ajax请求为例,你自己封装的ajax,使用方式如下:

ajax({
	url:'/getList',
	type:'Post',
	dataType:'json',
	data:{
		id:"123"
	}
})
.done(function(){})

但是这个时候你接到的项目当中都是:$.ajax({...}),这个时候我们只需要加一层适配器即可,代码如下:

let $ = {

ajax:function (options) {

return ajax(options);

}

}

Vue中的computed

Vue的计算属性相信大家在项目的开发当中都是经常会用到的一个特性,比如一个字符串我们想要他的翻转后的结果,那么这里就可以使用计算属性,计算属性这个特性本身用的设计模式就是适配器模式,代码如下:

<div id="example">
 <p>Original message: "{{ message }}"</p>
 <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
 el: '#example',
 data: {
 message: 'Hello'
 },
 computed: {
 // 计算属性的 getter
 reversedMessage: function () {
 // `this` 指向 vm 实例
 return this.message.split('').reverse().join('')
 }
 }
})

结果:

Original message: "Hello"

Computed reversed message: "olleH"

学好JavaScript设计模式,一文足矣!

 

装饰器模式

概念

装饰器模式,装饰我们可以理解为就是给一个东西装饰另外一些东西使其更好看,对应到前端中就是为对象添加新功能,并且不改变其原有的结构和功能,这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。在我们日常生活中用到的手机壳就是经典的例子

前端中的实例

1.ES7中的装饰器

ES7装饰器的具体用法可以去找阮一峰老师的相关文章,其实参照其字面意思,就是对类、方法、属性进行修饰,从而进行一些相关功能的定制。那么JavaScript的Decorator在原理和功能上简单明了,简单来说就是对对象进行包装,返回一个新的对象描述,这里可以类比高阶组件。这里我们列举一个简单的例子,想必大家都玩过王者荣耀,英雄的战斗力都是随着装备和等级的增加越来越厉害,那么这里我们就以王者荣耀当中的英雄为例,示例场景是这样的:

然后按照这个场景来写具体的代码:

创建Hero类

class Hero {
		constructor(define = 10,attack = 20, blood = 20) {
			this.init(define,attack,blood)
		}
		init(define,attack,blood) {
			this.define = define;
			this.attack = attack;
			this.blood = blood;
		}
		toString() {
			return `防御力: ${this.define},攻击力:${this.attack},血量:${this.blood}`
		}
	}
	let houyi = new Hero();
	console.log(`当前状态 ===> ${houyi}`) 
	//输出:当前状态 ===> 防御力:10,攻击力20,血量20

创建decorateCloth方法,为英雄增加布甲装备。

function decorateCloth(target,key,descriptor) {
		const method= descriptor.value;
		let moreDef = 100;
		let ret;
		descriptor.value = (...args) => {
			args[0] += moreDef;
			ret = method.apply(target,args);
			return ret;
		}
		return descriptor;
}
class Hero {
		constructor(define = 10,attack = 20, blood = 20) {
			this.init(define,attack,blood)
		}
		@decorateCloth
		init(define,attack,blood) {
			this.define = define;
			this.attack = attack;
			this.blood = blood;
		}
		toString() {
			return `防御力: ${this.define},攻击力:${this.attack},血量:${this.blood}`
		}
	}
	let houyi = new Hero();
	console.log(`当前状态 ===> ${houyi}`) 
	//输出:当前状态 ===> 防御力:110,攻击力20,血量20

可以看到输出结果防御力确实增加了,布甲确实起到了作用。

从上面的代码可以看出,如果有的时候我们并不需要关心函数的内部实现,仅仅是想调用它的话,装饰器能够带来比较好的可读性,使用起来也是非常的方便。

学好JavaScript设计模式,一文足矣!

 

代理模式

概念

代理模式是使用者无权访问目标对象,中间加代理,通过代理做授权和控制,我们经常会用到这个模式,不管实际的开发也好,还是网络部署当中,都能够看到它的身影。如:科学上网 谷歌搜索

前端中的实例

1.网页中的事件代理

其实网页的事件代理也是非常常考的面试题之一,其实就是把元素绑定到父元素上面,而不是对其下面的每一个子元素都进行相应的绑定,下面举一个具体的实例:

<div id="item">
	<a href="#">a1</a>
	<a href="#">a2</a>
	<a href="#">a3</a>
	<a href="#">a4</a>
<div>
<button>点击增加一个a标签</button>
<script>
	let div1 = document.getElementById('div1')
	div1.addEventListener('click',function(e) {
		let target = e.target
		if(e.nodeName === 'A') {
			alert(target.innerHTML)
		}
	})
</script>

2.jQuery中的$proxy

比如我们经常会遇到这样一种情况,如下代码所示

$('#div1').click(function() {
		$(this).addClass('red')
	})
	$('#div1').click(function() {
		setTimeout(function () {
			// this 不符合期望
			$(this).addClass('red');
		},1000);
	})

解决的方法可能有的同学已经想到是先将this赋值给一个变量。

$('#div1').click(function() {
	setTimeout(function () {
		let _this = this;
		// this 不符合期望
		$(_this).addClass('red');
	},1000);
})

是的这种方法是对的,但是这样就会增加一个变量,所以这里用$proxy解决更好,代码如下:

$('#div1').click(function() {
	setTimeout($proxy(function () {
		$(this).addClass('red');
	}),1000);
})

观察者模式

概念

观察者模式就是只要你作为订阅者订阅了某个事件,当事件触发的时候,发布者就会通知你。这里可以类比我们去咖啡厅点咖啡,当我们点了咖啡之后,就可以去做别的事情,当咖啡完成时,服务员就会叫你来取,你到时候取走咖啡即可。

前端中的实例

1.网页中的事件绑定

<button id="btn1">btn</button>
<scirpt>
	$('#btn1').click(function() {
		console.log(1)
	})
</script>

这里我们订阅了btn1的click事件,当几点btn1所在的元素时,click function就会触发。

2.Node.js的自定义事件EventEmitter

const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter();
emitter1.on('some',()=> {
	//监听some事件
	console.log('some event is occured 1')
})
emitter1.on('some',()=> {
	//监听some事件
	console.log('some event is occured 2')
})
emitter.emit('some')

状态模式

概念

提到状态模式就不得不提到有限状态机,阮一峰总结过有限状态机的三个特征:

这里充分说明了状态模式的重要性。一个对象有状态变化,每次状态变化都会触发一个逻辑,不能总是用if else来控制,更要学会用状态来进行控制。

前端中的实例

1.ES6中的promise

promise是ES6新增的一个特性,它是异步编程的一种解决方案,比传统的解决方案更加方便,可以使用.then()操作避免了回调嵌套带来的回调地狱式的写法,那么下面就来简单实现一个promise。

let fsm = new statemachine({
	init:'pending',
	transitions:[
		{
			name:'resolve',
			from:'pending',
			to:'fullfilled'
		},
		{
			name:'reject',
			from:'pending',
			to:'rejected'
		}
	],
	methods:{
		onResolve:function(state,data) {
			data.successList.forEach(fn => fn())
		}
		onReject:function(state,data) {
			data.failList.forEach(fn => fn())
		}
	}
})

这就是一个简单的promise。一个promise只有三种状态,所以promise可以说是状态模式的经典案例。

总结

这篇文章我们主要介绍了5大设计原则和7个设计模式,这7个设计模式是前端使用频率较高的设计模式,当然还有很多其他的设计模式,由于篇幅原因这里无法全部介绍,

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>