1.避免全局耦合
这应该是一种常见的耦合。全局耦合意味着几个类和模块共享全局变量或全局数据结构,尤其是当一个变量跨越几个文件时。例如,下面,一个变量是用html定义的:
& lt>。
varPAGE = 20
& lt/>。
& ltsrc="main.js">。& lt/>。
上面在head标签中定义了PAGE的一个全局变量,然后在main.js中使用,这样PAGE就是一个全局变量,跨越两个文件,一个html,一个js。然后main.js中弹出一个PAGE变量,这个代码的后续维护人员看到这个变量到处都找不到它的定义,最后搜索了很久才发现原来是在xxx.html的head标签中定义的。这有点蛋疼,这样的变量容易和局部变量产生命名冲突。
因此,如果需要在页面上写数据,一种改进的方法是在页面上写一个窗体,数据作为窗体中的控件数据写入,如下所示:
& ltformid="page-data ">。
& ltinput type = " hidden " name = " page " value = " 2 " & gt。
& lttext area name = " list " style = " display:none " >[{ " userName ":" yin " },{ }]& lt;/textarea>。
& lt/form>。
上面使用了Input和textarea,使用textarea的好处是支持特殊符号。序列化表单的数据,也比较简单,可以查看
第二个是全局数据结构,它可能使用模块化方法,如下所示:
//data.js
module.exports={
住宅列表:空
}
//search.js获取houseList的数据
var data = require(" data ");
data . house list = Ajax();
要求(“格式-数据”)。format();
//format-data.js格式化houseList的数据
functionformat(){
var data = require(" data ");
过程(数据);
要求(“显示结果”)。show();
}
//show-result.js显示数据
functionshow(){
显示数据(需要(“数据”)。houseList)
}
以上四个模块各有各的功能。乍一看好像没什么问题,但是都是用一个数据模块来共享数据。这个确实方便,但是是全局耦合的。因为使用了相同的数据,所以您不能保证其他人会加载这个模块并进行一些修改,或者在您的一个服务的异步回调中进行更改。第二个问题:你不知道这些数据是从哪里来的,可能是谁修改的。该过程对后续模块不透明。
因此,我们应该考虑使用传递参数的方法来降低耦合度,并将数据作为参数传递:
//移除data.js
//search.js获取数据并传递给下一个模块
varhouseList = Ajax();
要求(“格式-数据”)。格式(House LiST);
//format-data.js格式化houseList的数据
函数格式(houseList){
进程(House LiST);
要求(“显示结果”)。显示(House LiST);
}
//show-result.js显示数据
functionshow(houseList){
显示数据(住宅列表)
}
可以看出,从搜索中获取数据后,提交给格式数据进行处理,然后在格式数据处理后提交给显示结果。这样可以清楚的知道数据处理流程,保证houseList不会被一个异步回调意外更改。如果单看一个模块,模块show-result不需要关心houseList的流程和处理,只需要关心输入是否满足其格式要求。
这个时候你可能会有一个问题:既然这个数据已经被层层传递了这么多次,不如写一个像上面这样的数据模块,大家改一下就简单多了。是的,这很简单,但是一个数据结构跨几个文件使用,会造成我上面提到的问题。有时候可能会出现一些意想不到的情况,可能要找很久的bug。因此,这种解耦是值得的,除非你定义的变量不跨文件,并且它的作用域只在它所在的文件中,这样会好很多。或者数据是不变的,定义了数据中的数据后,值永远不会改变,这也应该是可取的。
2.js/css/html耦合
这种耦合在前端应该是最常见的,因为这三者通常有交集,需要使用js控件样式和html结构。如果用js来控制样式,很多人喜欢用js写样式。比如一个页面滑动到某个地方,就会吸到某个杠:
很多人可能会这样写:
$(".吧”)。css({
位置:固定;
top:0;
左:0;
});
然后当用户向上滑动时取消固定:
$(".吧”)。css({
位置:静态;
});
如果使用react,可能会设置一种状态数据的样式,但实际上都是一样的,css是混在js里的。想查你风格的人想给你换个bug。他检查了浏览器,发现标签样式中有一个属性。然后很长一段时间他都找不到设定在哪里。最后,他发现它被设置在一个js的隐藏角落里。你在js里设置样式,然后css里就会有样式。当你改变css的时候,如果不知道js中也设置了样式,可能会发生冲突,在一定条件下触发js中样式的设置。
因此,不建议直接在js中更改style属性,而是通过添加或删除类来控制样式,使样式返回到css文件。例如,上述内容可以更改如下:
//添加固定
$(".吧”)。addClass("固定");
//固定
$(".吧”)。removeClass("固定");
固定的风格:
. bar.fixed{
位置:固定;
左:0;
top:0;
}
如你所见,这个逻辑很清楚,当你回退固定时,不需要把它的位置恢复到静态,因为它不一定是静态的,也可能是相对的。当你以这种方式取消一堂课时,你不需要关心它是什么。
但是有一个是不可避免的,就是听滚动事件或者鼠标移动事件,动态改变位置。
这种控制类的方式的另一个优点是,当你动态地在容器中添加或者删除一个类时,你可以使用这个类,借助子元素选择器来控制它的子元素的样式,这也是非常方便的。
很多人可能会认为html和css/js是解耦的,也就是你不能用html写样式和标签,但并不是一切都是绝对的。如果有一个标签,它在一个字号上不同于其他标签,那么为什么不直接写一个字号的内嵌样式呢?性能方面,如果写一个类,就要和这个类匹配。或者你的html文件只有20或30行的css,那么就在head标签上加一个样式,直接写在head里,这样你就可以少管理一个文件,浏览器就不需要加载外部文件了。
有时候需要直接用html写标签。它的优点是不需要加载链外文件,处理速度会很快。几乎与dom渲染同时,这在解决页面闪烁时更有用。因为如果要用js动态改变加载的dom,肯定会在外链里面闪,而直接写就不会有这个问题,即使放在体后面。例如以下内容:
原始数据用p标注,但在textarea显示时需要换一行 r n,如果dom渲染后在外链更新了dom,会出现上面的闪烁。你可能会说我用的是react,数据是动态渲染的,渲染之前已经处理过了,所以不会出现以上情况。好吧,那么,至少你知道。
内聚与耦合相反,写代码的原则是低耦合高聚合。内聚是指一个模块的职责和功能非常紧密,不可分割,这个模块具有高度的内聚性。让我们从重复的代码开始:
3.减少重复代码
假设有一段代码要在另一个地方使用,但是不一样,那么最简单的方法就是复制然后修改。这也是很多人采用的方法,导致:以后想换同一个地方,就要同时换很多地方,很麻烦。
例如,有一个搜索界面:
请注意,发送方被单独打包到一个模块中,因为除了搜索发送方之外,还可以使用该请求。同时,搜索结果将使用两个显示的模板。
由于不止一个页面将使用搜索功能,请继续抽象上述内容,并将其打包到搜索应用模块中。需要使用的页面只需要需要这个搜索应用,调整它的init函数,然后传递一些定制的参数。这个搜索应用相当于一个搜索插件。
所以整个思路是这样的:有一个重复的代码-->:封装成一个函数-->:封装成一个模块-->:封装成一个插件,抽象层次不断提高,共性和差异分离。走在抽象封装的路上,也要走在大神的路上。
当然,如果两件事没有共同之处,但你坚持在一起,那就不可取。
我不是说你必须使用requirejs,es6的导入,或者webpack的require。关键是你要有这个模块化的思想,而不是工具。不管用哪一种,只要有这种抽象的想法,都是可取的。
模块化的极端是拆分粒度太细,一个简单的函数,一起写十行代码就可以完成,只写七八个函数栈,每个函数只有两三行。除了让你的逻辑过于复杂之外,也没多大好处。当你有重复的代码,或者一个函数太大,函数太多,或者逻辑写了三个循环然后嵌套了三个if,或者你预料到别人可能会用你写的东西的时候,考虑模块化拆分是合适的。
搜索结果和搜索ajax在功能上都是高度内聚的,每个模块都有自己的责任,不能分割。这叫面向对象编程中的单一责任原则,每个模块只负责一个功能。
再比如,我提到了一个上传和剪切的实现,包括剪切、压缩和上传、进度条三个功能,所以我把它分成三个模块:
这里说的大部分模块都是单实例对象,不会被实例化,一般能满足大部分需求。在这个单一的模块中,它自己的“私有”函数通常是通过传递参数来调用的,但是如果要传递的数据很多,就有点麻烦了。这时可以考虑封装成一个类。
3.封装成一个类
剪切并上传以上进度条中的进度条。一个页面上可能有几个地方可以上传。每个上传地点都会有自己的进度条。因此,不可能在文件的顶部定义一些变量,并像顶部所说的那样与该模块中的函数共享它们。只能以传递参数的形式,也就是在调用开始的时候定义一些数据,然后层层传递。数据多的话会有点麻烦。
稍微灵活一点,把进度条封装成一个类:
functionProgressBar($ container){
这个。$ container = $ container//进度条外的容器
这个。$ meter = null//进度条的可见部分
这个。$ bar = null//用于存储可见部分的进度条容器
这个。$ barFullWidth = $ container . width()* 0.9;//进度条宽度
this . show();//显示一个对象时显示new
}
或者你用的是ES6的类,但是本质上是一样的,然后这个ProgressBar的成员函数可以用这些定义的“私有”变量,比如设置进度条的进度函数:
progress bar . prototype . SetProgress = function(百分比,时间){
time = typeoftime = = = " undefined "?100:时间;
这个。$meter.stop()。动画({width:parseInt(this。$ barFullWidth *百分比)},时间);
};
这使用了两个私有变量。如果你把原来的两个相加,你必须通过传递参数来传递四个。
使用类是一种模块化的思想,另一种常见的是策略模式。
4.使用策略模式
假设您想要实现以下三个外壳:
这三个项目符号框在样式和功能上都是一样的,唯一的区别就是上面的标题文案不一样。最简单的可能就是复制每个项目符号框的html,然后再修改。如果使用react,可能会拆分组件,一个在上面,一个在下面,所以OK,这样做。如果您不使用react,您可能必须找到一种方法来组织您的代码。
如果你有战略模式的想法,你可以把上面的标题当成个人战略。首先,定义不同外壳的类型,并逐个标记不同的外壳:
varpopType=["register "、" favHouse "、" save search "];
定义三个poptype,分别对应上面三个框,然后每个pop type都有一个对应的副本:
Data.text.pop={
注册:{
titlte:“创建您的免费帐户”,
副标题:“搜索房屋和独家优惠清单”
},
favHouse:{title:"xxx ",Title:"xxx"},
saveSearch:{title:"xxx ",Title:"xxx"}
};
{标题:“”,副标题:“”}这被认为是播放盒子副本的策略,然后在编写盒子的html模板时引入了一个占位符变量:
& ltsection>。
{{title}}
{ {字幕}}
& ltdiv>。
& lt!-其他内容->:
& lt/div>。
& lt/section>。
渲染此项目符号框时,根据传入的popType映射到不同的文案:
functionshowPop(popType){
小胡子.渲染(popTemplate,Data.text.pop[popType])
}
这里,Data.text.pop[popType]映射到对应的文案。如果你用react把标题打包成一个组件,想法实际上是一样的。
但是,这并不是一个严格的战略模型,因为战略就是要有要执行的东西。我们在这里其实是一个死副本,但是我们用的是战略模型的思想。接下来,继续讨论使用策略模式来执行。
上面弹出框的触发机制是:用户点击注册,喜欢的房子,保存搜索条件。如果用户没有登录,将显示一个注册框。用户注册后,用户的原始操作,比如是否是集合,注册后一定要有回调,回调做的事情不一样。
当然,你可以在回调中写很多if else或case:
functionpopCallback(popType){
开关(popType){
案例“登记”:
//什么都不做
打破;
案例:“favHouse”:
fav house();
打破;
案例:“保存搜索”:
save search();
打破;
}
}
但是当你有很多案子的时候,可能看起来不是特别好,尤其是如果别的。此时,您可以使用策略模式,每个回调都是一个策略:
varpopCallback={
favHouse:function(){
//做某事。
},
saveSearch:function(){
//做某事。
}
}
然后根据popType映射调用相应的回调,如下所示:
varpopCallback = require(" pop-callback ");
if(TypeofOpcalback[PopType]= = " function "){
pop callback[pop type]();
}
这样就是一个完整的战略模式,有很多优势。如果以后需要添加弹出类型popType,只需在popCallback中添加一个函数,或者删除一个popType,相应注释掉一个函数即可。不需要改变原代码的逻辑,但是如果采用else,就必须修改原代码的逻辑,所以对扩展是开放的,对修改是封闭的,这就是面向对象编程中的开放封闭原则。
在js中实现策略模式或者其他设计模式是很自然的方式,因为js中的函数可以直接作为普通变量使用,而在C++/Java中则需要一些技巧和一些OO技巧。比如上面的策略模式,你需要用Java写一个接口类,定义一个接口函数,然后把每个策略封装成一个类,分别实现接口类的接口函数。js中的设计模式通常是用几行代码编写的,这也可能是函数式编程的优势。
前端和设计模式通常处理访问者模式
4.访客模式
事件监控是一种访问者模式。典型的访问者模式可以通过首先定义一个输入类并初始化它的访问者列表来实现
功能输入(inputDOM){
//用于存储访问者的数据结构
this.visitiors = {
"点击":[],
“改变”:[,
【特别】:[] //定制活动
}
this.inputDOM = inputDOM
}
然后提供一个添加访问者的外部界面:
input . prototype . on = function(event type,callback){
if(typeof this . visitiors[event type]!== "未定义"){
这次访问[事件类型]。push(回调);
}
};
消费者调用并传递两个参数,一个是事件类型,即访问类型,另一个是特定的访问者,这里是回调函数。输入将访问者添加到其访问者列表中。
同时,Input还提供了删除访客的界面:
input . prototype . off = function(event type,callback){
var visitors = this . visitors[event type];
if(typeofvisitiors!== "未定义"){
varindex=visitiors.indexOf(回调);
if(index >;=0){
visitiors.splice(index,1);
}
}
};
通过这种方式,Input已经与访问者建立了关系,或者访问者已经成功地订阅了来自接收者的消息,并且一旦接收者接收到消息,它将把它一个接一个地传递给它的访问者:
input . prototype . trigger = function(event type,event){
var visitors = this . visitiors[EventType];
var eventFormat =processEvent(事件);//获取消息并格式化它
if(访问者类型!== "未定义"){
for(var I = 0;i <。游客。长度;i++){
访问者[I](event format);
}
}
};
触发器可以由用户设置,也可以由底层控件调用。在其他领域,它可能由光敏控件触发。无论如何,一旦有人触发了触发器,接收者就会一条条地发出消息。
如果你知道事件监控的模式是这样的,可能对你写代码有帮助。例如,如果您单击下面搜索条件的x,您应该清除空上方的搜索框,触发搜索,并删除输入框右侧的x。有几件事要做。
这时你可能会这样写:
$(".图标-关闭”)。on("click ",function(){
$(这个)。父项()。移除();//删除您自己的演示文稿
$(" #搜索-输入")。val(" ");
searchajax . Ajax();//触发搜索
$("#clear-search ")。hide();//隐藏输入框x。
});
但其实这样有点麻烦,因为上面的搜索输入框肯定会相应操作。当用户输入空时,右边的X会自动隐藏,当输入框发生变化时,会自动搜索,也就是说所有的附加东西都已经存在于输入框中,所以只需要触发下方输入框中的变化事件即可:
$(".图标-关闭”)。on("click ",function(){
$(这个)。父项()。移除();//删除您自己的演示文稿
$(" #搜索-输入")。val(" ")。触发(“变更”);
});
当输入框为空时,搜索输入框会进行相应处理,以下条件所示的X不需要关注。触发更改后,相应的消息会发送给搜索输入框的访问者。
1.《耦合 【那些技术】什么是代码耦合》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《耦合 【那些技术】什么是代码耦合》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/jiaoyu/1604846.html