JavaScript 设计模式学习总结与感悟(开发&面试必备)
共 8950字,需浏览 18分钟
·
2020-08-31 09:18
作者:吃葡萄不吐番茄皮
原文地址:https://segmentfault.com/a/1190000019663847
前言
最近阅读了《JavaScript设计模式与开发实践》,收获颇丰,于是想写一点总结及感想
写一篇文章对于我的意义在于:
给别人讲述知识时可以发现自己掌握的是否牢固透彻,写的过程不断发现自己的不足,然后通过一些方式来解决问题,这也是一种学习过程;当然,写文章给别人看,也要从读者的角度出发,考虑他们想要从这篇文章获得什么,还有就是你想表达些什么给读者
这种过程大概叫费曼学习法,图解:
(图片来自网络,侵删)
这篇文章我想表达的是:学习设计原则设计模式的好处、介绍设计原则和设计模式、常用设计模式的实践、代码重构的具体方法、一些问题一些思考。你可以先读一遍带着疑问去阅读这本书籍或者阅读完书籍再来看这篇文章是否有助于你理解
为什么要学习设计原则、设计模式
首先,设计原则、设计模式受用的目标人群我觉得是有一定的js基础且有一定的项目实践经历的开发者,不然的话,就算学习设计也是生搬硬套,收货甚微,当有了一定基础及实践之后,阅读本书之后有三种感觉:
你的某些代码就是书上的反例,醍醐灌顶的感觉 你的某些代码已经实践了某些设计模式遵从了某些设计原则,但是你并不知道这样写代码是叫这个模式以及这个模式的全部优缺点或者你的代码还有更进一步优化的空间 内心冷笑一声:哼 so easy... emmmmm,如果大佬还愿意继续阅读本文的话,希望大佬可以在评论区指点一二
个人认为,JavaScript设计原则以及设计模式都只属于软件设计的一部分,但这意味着已经开始脱离了API调用工程师
的称号,开始接触编程思想,但是设计原则跟模式有限,只针对于代码层面。
打个比方:vue源码使用了xx模式,解决了xx问题,但是,在选择xx模式解决xx问题的背后又有多少思考?呢?简单猜测一下:一个框架or库 = 软件设计 + 其他框架优点借鉴 + 创新 + 编码 + 测试;本人水平有限,这是知识宏观的揣测一下实现一个框架or库需要的付出。请不要当真or较真
可能没人要你去写框架什么的,但是你负责的部分总是你来写来维护,不按套路出牌受伤的是自己 举这个例子是想说明:学习一下设计原则设计模式是多么的有必要(强行解释,最为致命)
二、JavaScript常用设计原则
我想提一个这本书的缺点,就是目录,设计模式都是要遵循设计原则的,而且很多设计模式章节都提到了设计原则,然而书的目录是最后一个大章节才说的设计原则,我觉得设计原则应该放在设计模式之前,所以建议先阅读设计原则再阅读设计模式,在理解上也会有帮助,至于第一章节的基础知识介绍,这个看各人情况可以选择要不要忽略
下面介绍常用的设计原则
1. 单一职责原则 SRP
【定义】
单一职责原则的定义是‘引起变化的原因’,如果你有两个原因去修改一个方法,说明这个方法有两个职责,承担的职责越多你需要修改他的可能性就越大,修改代码总是件危险的事情,特别是两个职责耦合在一起的时候一句话概括:一个对象(方法)只做一件事
【理解】
书上有介绍单一职责原则相关的模式,其实我觉得从原则去联系模式有点不合理,因为所有模式都会去遵从设计原则,知识侧重点不一样而已。所以我会在下个章节的模式里去联系原则,而说原则,我只想脱离模式单独去说原则的优缺点以及如何应用
【优点】降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。
【缺点】增加编码复杂度,同时增加对象之间联系的难度
【应用】煮个栗子,js的组件化开发,有一种思想就是组件的原子化,把组件拆到不能再拆的粒度,是最好维护的。但事实真的是这样吗,组件多了,组件之间的联系也多了,就意味需要管理更多的组件及组件间的复杂关系,有时候明明两个组件像孪生兄弟一样分不开,你偏要拆散他们并且给他们一座桥梁去联系,这无疑比让他们在一起成为一个整体要难维护的多,而且颗粒度越小,代表着开发成本越大。
单一职责原则也是如此,这些思想其实建立在理想化的状态上,而实际情况往往不会与理想对等,所以实际项目使用中,要学会灵活多变,知道什么时候其实是可以违反原则的,借用书上的话:
SRP 原则是所有原则中最简单也是最难正确运用的原则之一。
要明确的是,并不是所有的职责都应该一一分离
一方面,如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们。比如在 ajax 请求的时候,创建 xhr 对象和发送 xhr 请求几乎总是在一起的,那么创建 xhr 对象的职责和发送 xhr 请求的职责就没有必要分开。
另一方面,职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦 合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们,在代码需要重构的 时候再进行分离也不迟
个人认为这是理解最简单,实践最难的一个
// bad
var createLoginLayer = (function(){
var div;
return function(){
if ( !div ){
div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
}
return div; }
})();
// good
var getSingle = function( fn ){ // 获取单例
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
var createLoginLayer = function(){ // 创建登录浮窗
var div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
document.body.appendChild( div );
return div;
};
var createSingleLoginLayer = getSingle( createLoginLayer );
var loginLayer1 = createSingleLoginLayer();
var loginLayer2 = createSingleLoginLayer();
alert ( loginLayer1 === loginLayer2 ); // 输出: true
2. 最少知识原则 LKP
单一职责原则说道:一个对象(方法)只做一件事;那代表着我们要创建更多的对象(方法)来分解一个之前比较大的对象(方法),那分解之后,对象(方法)是小了,好维护好扩展了,但是对象之间的联系缺越来越多了,有两个对象非常耦合,怎么办
【定义】
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不 必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对 象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三 者对象来转发这些请求
**【优点】**减少或消除对象之间的耦合程度,提高复用性
**【缺点】**需要封装对象或者引入一个第三者对象来处理两者之间的联系,有时候第三者对象会复杂到难以维护
**【应用】**封装是广义上最少知识原则的一种体现,封装的好处是隐藏内部属性、方法、实现细节,只抛出指定数据或对象给外界,外界使用封装对象的对象越少说明联系就越少。另外一种就是两个对象很耦合的时候通过引入第三者来处理两个对象之间的关系
这个demo是我自己编的,大概的意思是一个班级有n个学生,学生有自己的年龄,年龄相等的学生可以做朋友(emmmm,原谅我奇葩的脑洞,还好我没说性别一样的才能做朋友,不然要被在座的各位打屎),如果每次新进来一个学生,都要全班一个学生一个学生的问年龄,那太麻烦了,如果老师入学的时候把学生按年龄分批了,那是不是很好找到,这个按年龄分批次就是引入的第三者来处理这个问题,我不知道够不够形象啊,但是书上的外观模式的代码举例真的辣鸡,不够形象?
let nameList = [{
name: 'eason',
age: 1
}, {
name: 'taylor',
age: 3
}, {
name: 'jack',
age: 2
}, {
name: 'yu',
age: 1
}, {
name: 'xixi',
age: 3
}]
let state = {}
let People = (name, age) => {
let that = {}
that.name = name
that.age = age
that.friends = []
return that
}
// bad
for (let n of nameList) {
state[n.name] = new People(n.name, n.age)
}
let jay = new People('jay', 3)
let syz = new People('syz', 2)
let keys = Object.keys(state)
for (let k of keys) {
if (state[k].age === jay.age) {
jay.friends.push(state[k].name)
}
if (state[k].age === syz.age) {
syz.friends.push(state[k].name)
}
}
console.log('jay-friends', jay.friends) // ["jay", "taylor", "xixi"]
//good
let ageList = []
let ageMap = {}
for (let n of nameList) {
state[n.name] = new People(n.name, n.age)
if (ageList.indexOf(n.age) < 0) {
ageList.push(n.age)
ageMap[n.age] = []
ageMap[n.age].push(n.name)
} else {
ageMap[n.age].push(n.name)
}
}
let addPeople = (name, age) => {
ageMap[age] = ageMap[age] || []
ageMap[age].push(name)
return new People(name, age)
}
let jay = addPeople('jay', 3)
let syz = addPeople('syz', 2)
console.log('jay-friends', ageMap[jay.age]) ["taylor", "xixi", "jay"]
console.log('syz-friends', ageMap[syz.age]) ["jack", "syz"]
3. 开放-封闭原则 OCP
【定义】
当需要改变一个程序的功能或者给这个程序增加新功 能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码我来强行解释一下,对扩展开放,对修改封闭
【优点】
程序的稳定性提升、容易变化的地方分离出来后更容易维护
【缺点】
代码的完全封闭几乎不可能,谁也没有’未卜先知‘的能力,但是我们可以尽可能的去容易变化和不容易变化的地方,挑出容易变化的地方进行封闭
【应用】
用对象的多态性消除条件分支(常用)
多态指的是:同一操作在不同的对象上展现出不同的结果 多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 我的理解是,只要有类似的行为(map.show instanceof Function)就可以去执行然后呈现出不同的结果,不必每个对象的’类型‘
代码对比:
// bad
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' ); }
};
var baiduMap = {
show: function(){
console.log( '开始渲染百度地图' ); }
};
var renderMap = function( type ){
if ( type === 'google' ){
googleMap.show();
}else if ( type === 'baidu' ){
baiduMap.show();
} };
renderMap( 'google' ); // 输出:开始渲染谷歌地图
renderMap( 'baidu' ); // 输出:开始渲染百度地图
// good
var renderMap = function( map ){
if ( map.show instanceof Function ){
map.show(); }
};
renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMap ); // 输出:开始渲染百度地图
封装变化
封装变化就是把可能变化的地方分离出去,这样只需要维护已经被分离出去的容易变化的地方 方式有:放置挂钩、回调函数等
原谅我懒...
此原则的相对性
总结:开放-封闭原则其实还是跟我们的程序的维护及扩展性相关的原则,设计原则基本是围绕程序的可读、可维护、可扩展、高性能、安全等方面来进行的(正确性有待考究,目前我是这么认为的,有不同意见欢迎评论区留言),设计原则这块说的都比较相对,就想上一张的最少知识和单一职责原则一样,实际项目想要的做到这些,只能是一定程度上的,因为有些原则就像一个主观题,是没有标准答案的,但是有迹可循
三、常用设计模式
从【定义】【理解】【应用】【优点】【缺点】五个维度来介绍这些设计模式及应用
1. 单例模式
【定义】
保证一个类仅有一个实例,并提供一个访问它的全局访问点
【理解】
从字面意思来看,单例意思是单个实例。从定义来看,一个类只有一个实例,但是对于JavaScript来说创造一个对象so easy,不必像别的语言一样必须先创建一个类再通过类创建对象,所以定义第一句对于js来说无异于脱了裤子放p,我们只需要保证一个唯一的对象即可。(很多书籍都是比较早,在ES6之前发行的,所以都没有类这一说,所以所有JavaScript关于js无类这一块,大家可以自行思考,有类的情况下应该是怎样的)为什么只能有一个实例?将全部职责集中在一个实例上(这种对象往往对比普通对象比较庞大复杂),这样就能避免无谓的浪费为什么要提供一个全局的访问点?因为只有一个实例啊,所以要保证想用的地方都能访问到
【应用】
个人觉得只要是那种使用相对频繁、功能强大、创建一次相对消耗性能多的都可以考虑使用单例模式,这里圈起来,要考!别以为自己有个有点复杂的对象就想说使用单例模式,你可以先看下window对象有多少属性多少方法还有一点就是,因为唯一的,而且创建消耗一点性能,我们可以在需要的时候去判断实例是否存在,不存在再创建,存在就直接使用,这种叫做惰性单例,比如xhr
【优点】唯一实例,节约内存开销;
【缺点】
违背 单一职责原则
,不好维护、不容易扩展不适用于容易变化的实例
2. 代理模式
【定义】
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
【理解】
明星有经纪人作为代理,自身太忙无暇处理or不方便露面or...的事物都由经纪人代为处理,明星可以跟代理人沟通(我要吃冰淇淋,我要演这个不演那个balabala),编程中的代理也是如此。代理分保护代理和虚拟代理,但是js中保护代理不容易实现,以为常用的是虚拟代理
【应用】
惰性加载,比如图片懒加载 缓存代理,比如http304、Memoization,不懂Memoization的可以移步我的JavaScript性能优化--算法和流程控制章节
【优点】
分解本体职责--单一职责原则;解决本体暂时性无法处理一些请求;节约性能
【缺点】
编码需要维护代理与本体的联系 编码需要保持代理和本体接口的一致性
3. 策略模式
【定义】
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
【理解】
把分支转成配置,利用配置参数可以减少很多分支当你的代码里有很多if else或者switch的时候,就该考虑一下策略模式是否可以替换那些分支了为什么要用策略模式替换那些分支,一般情况下,我们把某个职责委托给一个策略对象,策略对象下的每一个键值对是一个分支,命名规范的话,key也是一个很好的注释,对象比分支更易读易扩展代码量更少回忆一下开发-封闭的设计原则:对扩展开放,对修改封闭,把容易改变的地方分离出来;策略对象就是被分离出来的容易被改变的地方
demo: 从上海出发到伦敦
// bad
if (money === 100) {
way = train()
} else if (money === 200) {
way = car()
} else if (money === 300) {
way = ship()
} else if (money === 400) {
way = flight()
}
// good
const wayMap = {
m100: train(),
m200: car(),
m300: ship(),
m400: flight()
}
【应用】
表单校验、算法分类、其他选择性较多且目标相同的需求
【优点】
避免多重条件选择语句 遵循开发封闭原则,代码更易阅读、维护和扩展,一定程度上减少代码数量 提高可复用性
【缺点】
违背最少知识原则,使用策略对象处耦合性大
4. 状态模式
【定义】
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变
【理解】
状态模式跟策略模式在实现上有一定的相似之处,但是目的不一样,策略模式每条策略都是平行平等的,而状态模式最大的区别是所有状态都是预先定义好的煮个栗子?:电风扇的1-5挡是状态模式,1-5挡是预先定义好的,结果是风力大小不一样,从上海到伦敦旅游,选择的交通方式是策略模式,坐汽车、火车、飞机、船是平行的,结果是你到了伦敦改变行为发生在状态模式内部,而策略模式是外部选择控制的,用户也不需要了解状态模式的内部细节 也就是说状态模式是遵循最少知识原则的
【应用】状态机,把状态抽离出来单独封装在一个对象里面,直观方便,只需call(状态机)一下使用
demo: 每个年龄段都要去做相对应的事
// bad
if (age === 7) {
work = primarySchool()
} else if (age === 13) {
work = juniorMiddleSchool()
} else if (age === 16) {
work = highSchool()
} else if (age === 19) {
work = university()
} else if (age === 23) {
work = growUP()
} else if (age > 24) {
work = makeMoney()
}
// good
const workMap = {
age7: primarySchool(),
age13: juniorMiddleSchool(),
age16: highSchool(),
age19: university(),
age23: growUP(),
ageMore: makeMoney(),
}
【优点】容易增加新状态或者切换当前状态 请求动作跟状态中封装的行为解耦 易维护扩展
【缺点】
逻辑分散不易读,状态多比较枯燥(if else更枯燥?)
5. 享元模式
【定义】
享元(flyweight)模式是一种用于性能优化的模式享元模式的核心是运用共享技术来有效支持大量细粒度的对象
【理解】
为什么说是一种性能优化模式?很多人喜欢在for循环里new一个对象,然后利用循环对象的当前值去改变这个对象再去写一些逻辑,其实很多时候这些对象改变之后还是有很多的相似之处的,享元模式就是把这些在循环里创建的对象提取到循环外层创建一次,然后循环内每次做一定改动再去写xx逻辑(这里只是举例循环,享元模式可以在很多地方使用不限于循环) 为了帮助理解(其实我觉得这个模式很好理解),书上举例说:有100种衣服,让100个模特一人试一件 和 一个模特试100件,假如请一个模特是100块(创建对象消耗),你愿意请多少个模特,一个模特试100件衣服肯定比100个模特慢,所以,享元模式是一种利用时间换取空间的优化模式
一个测试:像let const 这种块级作用域,代码块执行完会立即释放变量内存的
let aaa = () => {
debugger
for (let i = 0; i < 4; i++) {
let x = new Object({name: i})
console.log(x)
}
}
aaa() // {name: 0} {name: 1} {name: 2} {name: 3}
let aaa = () => {
debugger
for (let i = 0; i < 4; i++) {
let x = {name: i}
console.log(x)
}
}
aaa() // {name: 3} {name: 3} {name: 3} {name: 3}
这两种其实是一样,除了对象创建的方式不同,其他一样,对比是让你明白,第一种方式也会创建一个新的对象,x指针的指向是新创建的对象的引用地址
【应用】
如果考虑需要复用元对象的话,建议定义好元对象,然后使用的地方深拷贝或者浅拷贝一下再使用,这样其他地方也可以使用而不破坏元对象啦,注意JSON copy的坑
let obj = {
name: 'xx',
age: 0,
aaa: 111,
bbb: 222,
ccc: 333
}
let a = Object.assgin({}, obj)
// let a = JSON.parse(JSON.stringify(obj))
let aaa = key => {
console.log(key.age)
}
for (let i = 1; i < 5; i++) {
a.age = i
aaa(a)
}
其实我觉得享元模式跟单例模式有一定的相似之处的,虽然书上没提,但我真觉得这俩模式真的很像,单例模式被copy一下就是享元模式了,但是单例还是有特殊之处的,所有的状态属性方法都归于一个对象or实例上,并且提供全局的访问接口,想想都很强大,但是也比较难以维护
适用性:
【优点】都说了是一种性能优化模式了,那有点肯定就是性能优化了,解决大量对象带来的性能问题【缺点】缺点就是你要花时间学习并理解它 --哈撒给
6. 职责链模式模式
【定义】
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
【理解】
打个比方,有个魔方,100个人里面有10个人会解这个魔方,现在魔方在你手上,你不知道谁会解魔方,但是你知道某几个人可能会解魔方,于是你把魔方传给A,A不会再传给B(被传的都是已知有可能会解魔方的人)直到有人解开魔方某些场景下你需要费很大力气才能建立请求者和接收者之间的联系,这样使两者直接需要很大的耦合性,而使用职责链模式可以解除这种耦合作用域、原型链、事件冒泡跟职责链是不是很相似,个人觉得跟promise也有点相似,从接口获取到数据然后一层一层的promise去筛选过滤最后达到业务层,业务层拿到数据处理业务逻辑or呈现数据
【应用】
管理代码、其他类似事件冒泡的需求,说实话 我几乎没用过这个模式
【优点】
降低发起请求的对象和处理请求的对象之间的耦合性
【缺点】
链条太长对性能有较大影响
7. 装饰者模式
【定义】
装饰者模式可以动态地给某个对象添加一些额外的职责装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责
【理解】
钢铁侠捡到了灭霸的手套?并打了个响指
【应用】demo:
var plane = {
fire: function(){
console.log( '发射普通子弹' ); }
}
var missileDecorator = function(){ console.log( '发射导弹' );
}
var atomDecorator = function(){ console.log( '发射原子弹' );
}
var fire1 = plane.fire;
plane.fire = function(){ fire1();
missileDecorator(); }
var fire2 = plane.fire;
plane.fire = function(){ fire2();
atomDecorator(); }
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
使用AOP装饰函数
把这段看懂就ok了
【优点】扩展性好、随变化而变化,灵活、甚至预测需求
【缺点】暂时没想到...
8. 发布-订阅模式模式
【定义】
发布—订阅模式(其实也有一种叫观察者模式的模式,跟这个很像,但是没有中间的调度层),它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布—订阅模式
【理解】
Vue的Bus通信就是一种发布-订阅模式,中间的vue实例就是调度层,但是这个必须得先监听再推送,推送过的再添加监听就监听不到了而观察者模式指的是观察者与被观察者之间直接的通讯
【应用】
发布者的每次发布可以缓存起来,等有新的订阅者时,再把之前缓存的消息一起发送给新的订阅者,当然这只是有这个需求才这么做,没有的话就不用缓存了 现在流行的MVVM框架源码都有用到发布-订阅模式 addEventListener,添加事件监听
【优点】可以随意增加或删除订阅者,这不会影响到发布者的代码编写,耦合低
【缺点】消耗时间、内存、嵌套过深导致难以追踪
四、编程技巧
1. 提炼函数
单一职责原则 如果一个函数过长,不得不通过若干注释来显得易读,说明这个函数需要分解 好处:避免超大函数,代码复用,利用修改或覆盖,良好的函数命名也是一种注释 编码规范:理论上函数不超过50行,最好不超过20行
2. 合并重复的条件片段
定义:条件分支语句存在重复的时候,有必要进行去重工作 demo:
// bad
var paging = function(currPage) {
if (currPage <= 0) {
currPage = 0
jump(currPage)
} else if (currPage <= 1) {
currPage = 1
jump(currPage)
} else {
jump(currPage)
}
}
// good
var paging = function(currPage) {
if (currPage <= 0) {
currPage = 0
} else if (currPage <= 1) {
currPage = 1
}
jump(currPage)
}
3. 把条件分支语句提炼成函数
复杂的条件语句是难读的 可以把这句代码提炼成一个单独的函数,既能更准确地表达代码的意思, 函数名本身又能起到注释的作用 我加一句,如果需要修改条件的话,这样写也是更容易维护的
4. 合理使用循环
对于重复性工作,建议使用循环 比如:把key都放在一个数组里面,然后循环数组,new个key就可以了 有时候新增修改删除key等很方便
5. 提前让函数退出代替嵌套条件分支
一个函数只有一个入口,但是可以有多个出口 所以不必把所有的逻辑进行完,最后return,如果不关注剩下的逻辑,在满足条件的时候就可以return,这样做的目的不是程序少执行了哪几步,而是代码更简洁清晰易读
6. 传递对象参数代替过长的参数列表(面向对象编程--编程模式--配置对象)
这个在面向对象编程的编程模式章节有说,是编程模式的一个case,叫做配置对象,也就是把若干个参数放到到一个对象里面,成了一个集合,这个对象可以叫做配置对象
不用考虑参数的顺序 可以跳过某些参数的设置 函数扩展性更强,可以适应将来的扩展需要 代码可读性更好,因为在代码中我们看到的是配置对象的属性名称
7. 尽量减少参数数量
一句话:不要传没必要的参数,如果是备着以后扩展的话,等到扩展的时候再加也不迟
8. 少用三目运算符(三元运算符)
有一些程序员喜欢大规模地使用三目运算符,来代替传统的 if、else。理由是三目运算符性 能高,代码量少。不过,这两个理由其实都很难站得住脚(这是书上原话)
关于简洁性,三元运算符绝对比if else简洁,但是要分场合使用,条件分支逻辑简单的时候建议用三目运算符,但是条件分支逻辑复杂的时候,易读跟维护性都是很差的,比如:
对于性能,我做了个简单的测试,如图,0.2ms 除以 百万 是可以忽略不计的,所以性能上几乎没差的
9. 合理使用链式调用
书上说的链式调用(类似这个:console.log( new User().setId( 1314 ).setName( 'sven' ) );)节约的字节数量微不足道,链式调用的坏处就在不好调试上,我看chrome上一般都把错误一层一层的细化,然后我测试了一下这种链式调用会不会具体到其中一个方法,结果是不会,如图;(以前编码肯定也遇到过链式调用报错,但是没注意到这个细节,作者连调试难度都考虑到了,在下跪拜...)
如果该链条的结构相对稳定,后期不易发生修改,那么使用链式调用无可厚非。但如果该链 条很容易发生变化,导致调试和维护困难,那么还是建议使用普通调用的形式 一句话总结:稳定的链条可以写,不稳定的不要写
10. 分解大型类
这个我不太想赘述,感觉很多的原则or模式都有点似曾相识的感觉,这个一看不还是单一职责原则,大拆小,小的组合成大的,到底还是为了单个小功能的可读可维护扩展等等更好
11. 用return退出多重循环
假设在函数体内有一个两重循环语句,我们需要在内层循环中判断,当达到某个临界条件时退出外层的循环。我们大多数时候会引入一个控制标记变量 这玩意我以前好像就是这么处理的,引入一个标志位,外层循环每次都判断这个标志位,符合条件就结束循环
11. 结束语
整篇写完自己对设计原则和部分设计模式有了更深的了解,文中有不当之处欢迎评论区指正, 写完我有个反思,有的时候写东西喜欢叨叨,生怕别人没看到没看懂,以后一定精简
扫码关注公众号,订阅更多精彩内容。