第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 然后不断总结代码。