Skip to content

第11课 从两个需求出发,练就高质量代码

统计信息:字数 7133 阅读15分钟

【课程大纲】

1、如何保障代码的健壮性(函数参数验证,对象属性验证,调用函数前验证)

2、如何优化复杂的语句结构(享元模式优化重复代码,策略模式优化多重分支)

3、如何优雅的扩展和修改(命令模式实现模块完美封装)

【主要知识点】数据驱动;命令模式;如何写好功能

高级前端和初级前端的差距在哪里?功能基本相同,代码实现不同;

代码不好特点:拿到需求后,直接写代码实现功能,不思考怎样写;

实际运行中bug比较多,而且改bug比较复杂;

代码的扩展性和移植性较差(新功能或者更改已有功能很复杂)

01 需求功能到模块实现

需求:可以数值,可以左右移动DIV;可以撤销和前进。如果不使用框架,原生JS怎样实现?

界面DIV移动模块,输入指令模块,状态记录模块(undo-redo)(两个输入模块,一个状态数据模块,一个界面模块div);模块之前怎样消息沟通

拿到需求后,不是马上去写,而是想想划分成哪些模块实现这个功能:事件驱动(原生JS)-数据状态驱动(React)

事件触发,改变数据,映射器,反映到DOM(操作数据较简单,操作DOM性能较复杂)React 的核心思想(状态驱动-数据驱动)事件绑定和函数流程分离。(函数最小的实现功能的单元)

// 模块低耦合(数据层和视图层分离)
function changediv(dataObj) {
  // parameter validate
  if (!dataObj instanceof DataManager) {
    throw new Error('dataObj must be an instance of DataManager');
  }
  let _data = dataObj.getData();
  dom.style[_data.property] = _data.num;
}

function DataManager() {
  this.stateArr = [{property: 'left', num: 0}];
  this.nowState = 0;
}

DataManager.prototype.pushState = function(data) {
  this.stateArr.push(data);
  this.nowState = this.stateArr.length - 1;
}

DataManager.prototype.getBack = function() {
  if (this.nowState > 0) this.nowState--;
}

DataManager.prototype.getFront = function() {
  if (this.nowState < this.stateArr.length - 1) this.nowState++;
}

DataManager.prototype.getData = function() {
  return this.stateArr[this.nowState];
}

let DataManagerObj = new DataManager();
document.getElementById('toleft').onclick = function() {
  DataManagerObj.pushState({property: 'right', num: input.value });
  changediv(DataManagerObj);
}

命令模式

命令模式:把具体的指令和实现分离,对调用和执行解耦

做法:把方法数据封装到单一的对象内部,调用方和执行方实现解耦,职责分离原则。

解决的问题:只需要执行的操作(API),但是不需要知道具体执行操作的对象(API内部原理)。我们把所有的操作封装到单一对象中,然后发送一个指令,这个单一对象解决具体的命令实现。

使用命令模式对上面代码进行优化

// 匿名函数自执行,返回下面的对象(内含不同的函数)
return {
  execute: function(commander) {
    let commanderArr = ['back', 'front'];
    if (typeof commander === 'object') {
      DataManagerObj.pushState(commander);
      changediv(DataManagerObj);
    } else {
      let has = false;
      if (commanderArr.includes(commander)) {
        has = true;
        // 使用策略模式优化
        let state = {
          front: function() {
            //
          },
          back: function() {
            //
          },
          any: function() {
            //
          },
        };
        // 如果使用 if-else 多重条件判断,代码分支较复杂
        state[commander];
      }
    }
  }
}

// 外部调用
let input = document.getElementById('num');
document.getElementById('toLeft').onclick = function() {
  handleDiv.execute({property: 'right', num: input.value + 'px'});
}
document.getElementById('toRight').onclick = function() {
  handleDiv.execute({property: 'left', num: input.value + 'px'});
}
document.getElementById('back').onclick = function() {
  handleDiv.execute('back');
}
document.getElementById('front').onclick = function() {
  handleDiv.execute('front');
}
// 不足之处:接口传入的数据类型可能是对象或者是字符串

策略模式

使用对象存储每一种情况,代替多层的 if-else。代码扩展后,只需要扩展对象的属性值和对应的处理函数,不需要新加入else-if造成逻辑混乱。(switch也是类似的)

需求2

需求:做一个画廊效果:后端提供图片的数量和信息。前端可以选择排列顺序;每个图片下面有说明文字。

模块化思考:命令解析模块;生成DOM节点;渲染DOM(React框架中,后两个部分框架完成功能实现)

输入的配置,和默认的指令合并,然后实际执行(有的指令不需要用户直接输入;防止用户漏输入,函数的健壮性,设置组件的 defaultConfig defaultProps)

function Picture(commander) {
  this.html = '';
  this.render(commander);
}

Picture.prototype.initData = function(commander) {
  // 参数预处理
  const defaultConfig = {
    data: [],
    id: document,
    way: 'normal',
    size: [100, 100],
  };
  let final = {};
  for (let item in defaultConfig) {
    if (commander[item]) {
      final[item] = commander[item];
      if (item === 'id') {
        final.id = document.getElementById(commander.id);
      }
    } else {
      final[item] = defaultConfig[item];
    }
  }
  return final;
}

Picture.prototype.initDom = function(commander) {
  const styleArr = [
    {
      float: 'left',
      position: 'relative'
    },
    {
      width: '100%',
      height: '100%'
    },
  ];
  let wraper = document.createElement('div');
  let commanderHandle = {
    normal: function(arr) {
      return arr;
    },
    inverted: function(arr) {
      return arr.reverse();
    },
  };
  let _data = commander.data.forEach((url, index) => {
    let div = document.createElement('div');
    let span = document.createElement('span');
    let styleObj = null;
    let handleDom = null;
    styleArr.forEach((style, index) => {
      switch(index) {
        case 1:
          handleDom = div;
            break;
        case 0:
          handleDom = span;
          break;
      }
    })
  })
}

Picture.prototype.renderDom = function() {
  //
}

Picture.prototype.render = function(commander) {
  let order = this.initData(commander);
  this.initDom(order);
  this.renderDom(order.id);
}

new Picture({
  el,
  data,
  ...
});

总结

先学习怎样写好代码,然后学习怎样学习框架,框架和库是学不完的!(精通一个框架)自己写完代码,一句一句从微观的角度优化代码,然后从组件模块功能的角度整体代码优化(重构),然后使用一些实际的设计模式,这样才能提升自己。尝试使用新的语法和效果。使用设计模式和编程思维优化代码;写完界面,然后 code review 然后不断总结代码。


Last update: November 9, 2024