js中的设计原则和编程技巧

1 设计原则

1.1 单一职责原则(SRP)

含义:每个对象/方法只做一件职责。

例子:单例模式下,创建div
做法:将获取单例和创建div分离开来
实现:

var getSingle = function(fn:Function) {
  var result:any
  return function () {
    return result || (result = fn.apply(this,arguments))
  }
}

var createDiv = function () {
  var div = document.createElement('div')
  div.innerHTML = '这个是单例模式创建的div'
  document.body.appendChild(div)
  return div
}

var createSingleDiv = getSingle(createDiv)
var div1 = createSingleDiv()
var div2 = createSingleDiv()
div1 === div2 //true

好处:

  • 降低了单个方法/对象的复杂性
  • 有利于代码的复用和单元测试
  • 当一个职责发生变化时,不会影响到其他的职责。

职责分离原则:

  • 职责之间相互独立,不会相互影响
  • 如果两个职责总是同时发生变化,就没必要去分离
  • 如果在一起的几个职责不会发生任何变化,就没必要去分离

坏处:

  • 增加代码的复杂度
  • 增大了对象之间相互联系的难度

1.2 最少知识原则(LKP)

含义:一个软件实体应当尽可能少地与其他实体发生相互作用,软件实体包含:对象、类、模块、系统、变量、函数等。

例子:中介者模式,商城购买,购买手机 ,可以选择手机的颜色、购买数量、手机内存,当库存充足,购买按钮可点击 ,当库存不充足,按钮不可点击,并显示库存不足的信息
做法:输入框和下拉框发生事件时,只需要通知中介者它们发生了改变,让中介者来执行接下来的行为。
实现:
index.html

<body>
  选择颜色: <select id="colorSelect">
    <option value="">请选择</option>
    <option value="red">红色</option>
    <option value="blue">蓝色</option>
    </select>
    选择内存: <select id="memorySelect">
    <option value="">请选择</option>
    <option value="32G">32G</option>
    <option value="16G">16G</option>
    </select>
    输入购买数量: <input type="text" id="numberInput"/><br/>
    您选择了颜色: <div id="colorInfo"></div><br/>
    您选择了内存: <div id="memoryInfo"></div><br/>
    您输入了数量: <div id="numberInfo"></div><br/>
    <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button>
  <script src="./main.js"></script>  
</body>

main.js


var goods = { // 手机库存
  "red|32G": 3,
  "red|16G": 0,
  "blue|32G": 1,
  "blue|16G": 6
};

Function.prototype.after = function (fn) {
  var self = this;
  return function () {
    var ret = self.apply(this, arguments);
    if (ret === 'nextSuccessor') {
      return fn.apply(this, arguments);
    }
    return ret;
  }
};

var colorCheck = function (obj,color,memory,number,stock,nextBtn) {
  if (obj === colorSelect) { // 如果改变的是选择颜色下拉框
    colorInfo.innerHTML = color;
  }
  if(!color) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择手机颜色';
  } else {
    return 'nextSuccessor'
  }
}

var memoryCheck = function (obj,color,memory,number,stock,nextBtn) {
  if (obj === memorySelect) {
    memoryInfo.innerHTML = memory;
  }
  if(!memory) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请选择内存大小';
  } else {
    return 'nextSuccessor'
  }
}

var numCheck = function (obj,color,memory,number,stock,nextBtn) {
  if (obj === numberInput) {
    numberInfo.innerHTML = number;
  }
  if(!Number.isInteger(number - 0) || number <= 0) {
    nextBtn.disabled = true;
    nextBtn.innerHTML = '请输入正确的购买数量';
  } else if(number > stock){
    nextBtn.disabled = true;
    nextBtn.innerHTML = '库存不足';
  } else {
    nextBtn.disabled = false;
    nextBtn.innerHTML = '放入购物车';
  }
}

var checkBtn = colorCheck.after(memoryCheck).after(numCheck)

var mediator = (function () {
  var colorSelect = document.getElementById('colorSelect'),
    memorySelect = document.getElementById('memorySelect'),
    numberInput = document.getElementById('numberInput'),
    colorInfo = document.getElementById('colorInfo'),
    memoryInfo = document.getElementById('memoryInfo'),
    numberInfo = document.getElementById('numberInfo'),
    nextBtn = document.getElementById('nextBtn');
  return {
    changed: function (obj) {
      var color = colorSelect.value, // 颜色
        memory = memorySelect.value, // 内存
        number = numberInput.value, // 数量
        stock = goods[color + '|' + memory]; // 颜色和内存对应的手机库存数量
      checkBtn(color,memory,number,stock,nextBtn)
     
    }
  }
})();


// 事件函数:
colorSelect.onchange = function () {
  mediator.changed(this);
};
memorySelect.onchange = function () {
  mediator.changed(this);
};
numberInput.oninput = function () {
  mediator.changed(this);
};

好处:

  • 减少了对象之间的依赖

原则:

  • 尽可能地减少对象之间的交互,如果两个对象之间没必要直接通信,可以引入一个第三者对象来承担两个对象间的通信
  • 当一个对象必须引用另一个对象的时候,让对象只暴露必要的接口,让对象之间的联系限制在最小的范围内(广义)

坏处:

  • 有可能增加一些庞大到难以维护的第三者对象

1.3 开放封闭原则(OCP)

含义: 软件实体应该是可以扩展的,但不可以修改,软件实体包含:对象、类、模块、系统、变量、函数等。当需要修改一个程序的功能或者给这个程序增加新的功能时,可以使用增加代码的方式,而不是修改代码的方式。

例子:将一个数组映射为另一个数组
做法:映射的步骤是不变的(循环遍历),映射的方法是可变的,将映射的方法放在回调函数中封装起来
实现:

var arrMap = function(arr,callback) {
  var i = 0,
    length = arr.length,
    value,
    ret = []
  for(;i<length;i++) {
     value = callback(i, arr[i])
     ret.push(value)
  }
  return ret
}

var a = arrMap([1,2,3],function (i,n) {
  return n * 2
})

console.log(a)//[2,4,6]

好处:

  • 避免修改源代码可能造成的副作用
  • 降低维护源代码的成本
  • 将系统中稳定的部分和容易变化的部分分离开来 ,方便了以后替换更改变化的部分

原则:

  • 利用多态性(把做什么和谁去做分离开来),找出程序中将要发生变化的地方,将这些变化封装起来
  • 在不可避免修改的情况下,尽量修改相对容易修改的地方

2 编程技巧

2.1 面向接口编程

含义: 面向接口编程就是面向抽象编程,关注点从对象的类型上 转移到对象的行为上,针对对象的超类型的抽象方法编程。

接口既指对象响应的请求的集合,同时也指一些语言提供的关键字,如java的interface,专门负责建立类与类之间的联系

2.1.1 抽象类

  • 当泡茶和泡咖啡都有将原料倒入水中的操作时,我们可以将泡茶和泡咖啡向上转型为泡饮料(体现了对象的多态性)
  • 在泡饮料的类中写一个将原料倒入水中的抽象方法,同时让从泡饮料继承来的泡茶和泡咖啡的子类重写将原料倒入水中的方法
abstract class Beverage {
  abstract operation():void
}


class Tea extends Beverage{
  operation() {
    console.log('将茶包放入水中')
  }
}

class Coffee  extends Beverage {
  operation() {
    console.log('将咖啡放入水中')
  }
}

2.1.2 接口

2.2的例子也可以将泡饮料抽象为一个接口:

interface Beverage {
  operation: Function
}

class Tea implements Beverage{
  operation() {
    console.log('将茶包放入水中')
  }
}

class Coffee implements Beverage {
  operation() {
    console.log('将咖啡放入水中')
  }
}

2.2 代码重构

2.2.1 提炼函数

如果函数中有一大段代码可以被独立出来,最好是将这段代码放入另外一个独立的函数中

例子:页面加载完成后既要创建一个圆形,又要打印一些页面的版权信息
做法:将创建圆形和打印版权信息分离开来
实现:

window.onload = function () {
  createCircle()
  log()
}

function createCircle() {
  var canvas = document.getElementById('canvas')

  if(canvas.getContext) {
    var ctx = canvas.getContext('2d')

    ctx.fillStyle='red';
    ctx.arc(20,20,20,0,2*Math.PI)
    ctx.fill()
  }
}

function log() {
  console.log('版权所属')
}

2.2.2 合并重复的条件片段

如果每个if else判断里都执行同一段代码,可以将这段代码写入一个单独的函数,并且从判断中抽离出来,写在判断语句结束后
例子:跳转页面,当当前页面为非正整数时,跳转到第一页;当前页面大于总页数时,跳转到最后一页;其余情况,正常跳转
做法:将跳转页面抽离出来,放在判断语句之后
实现:

function paging(currPage,totalPage) {
  if(currPage <= 0) {
    currPage = 1
  } else if (currPage >= totalPage) {
    currPage = totalPage
  }
  jump(currPage)
}

2.2.3 把条件分支语句提炼成函数

当一个判断语句过长时,可以将该语句抽离成一个函数
例子:当当前活动为已开始并且用户已经报名或者当前活动已结束时,用户才可以在当前活动详情下进行晒图
做法:将判断语句抽离出来
实现:

function canPhoto (activityState,userState) {
  return (activityState === '已开始' && userState === '已报名') || activityState === '已结束'
}

function photoActivity(activityState,userState) {
  if(canPhoto(activityState,userState)) {
    doSomething()
  }
}

2.2.4 合理循环

如果有些语句做的是一些重复性的工作,可以将工作放入一个数组中,进行循环
例子:创建XHR对象(IE9以下的浏览器)
做法:将创建对象的参数放入数组中进行循环
实现:

var createXHR = function () {
  var versions = ['MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
  for (var i = 0, version; version = versions[i++];) {
    try {
      return new ActiveXObject(version);
    } catch (e) {}
  }
};
var xhr = createXHR();

2.2.5 用函数退出来代替嵌套条件分支

当条件嵌套太多层时,可以将这些条件尽可能地抽离成一个层级的条件分支,在进入一些条件分支时,可以让函数立即退出

2.2.6 传递对象参数代替过长的参数列表

当参数列表过长时,可以考虑将参数放入一个对象中,这样不用担心参数的数量和顺序

比如在筛选文件的时候,我们可以通过文件的创建时间、修改时间、类型、创建者、关键字、下载量等信息进行筛选,就可以将这些参数放在一个文件的对象中

2.2.7 尽量减少参数数量

当有的参数可以通过内部计算获得的,尽量减少这些参数,而是在函数内部直接通过计算获取

比如,在绘制正方形的时候,传入的参数有宽度、高度和面积,但是面积其实可以通过宽度和高度运算得来,因此传入的参数中可以去掉面积这个参数

2.2.8 少用三目运算符

三目运算符增加了代码的可读性和可维护性,但是省略的代码量忽略不计

a === b ? a : b === doc ? b : doc === 'text' ? doc : null

2.2.9 合理利用链式调用

链式调用在调试的时候很不方便,当一条链发生错误,要把这条链全部加拆开加上debugger和一些console才能定位到错误的位置

链式调用可以参考jquery

2.2.10 分解大类

将大类中的行为分解到粒度更细的对象中

2.2.11 用return退出多重循环

在需要中止多重循环时直接退出整个方法,将中止后要执行的函数或者代码放在return 的后面

例子:找到相加为15的两个小于10的数
做法:双重循环
实现:

for(var i = 0; i < 10; i ++ ) {
  for(var j = 0; j < 10; j ++) {
    if( i + j === 15) {
      return console.log(i,j)
    }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,521评论 0 5
  • 一个苏式园林为中心的旅游区 这个园林一进门在内外门之间的区域有一个谜一样的祈福架,一个说明台,和一块本来放着一个棺...
    白病知阅读 433评论 1 1
  • 早上准时起床,读经书,中午爸爸接的他,去姥姥家走亲戚,下午因为单位要考试,还是爸爸送的他,下午考完试,我...
    娟子_9cbc阅读 57评论 0 0
  • 为你披一身星光月色 星星还在天空快乐的眨眼睛 月儿已累弯了脊梁 可我还在四处奔忙 生活的煎熬 早已把我熬成老妇的模...
    墨涵自习室阅读 355评论 0 1