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

2018-09-25  本文已影响0人  月离丶

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)
    }
  }
}
上一篇下一篇

猜你喜欢

热点阅读