在JavaScript开发中,“封装”是重要的编程思想,但过度封装反而会增加代码冗余。尤其是在处理二维数组排序这类场景时,“部分封装核心逻辑+灵活调用”的方式,比“全流程封装成单一函数”更能减少代码量、提升灵活性。本文将结合你的二维数组排序代码,解析“按需封装”的优势,优化实现逻辑,并总结实用的封装原则。
一、你的代码核心思路解析:部分封装的合理性
先看你的原始实现,核心逻辑已经体现了“按需封装”的智慧——只将“单个一维数组排序”封装成函数,而二维数组的遍历、数据输出等流程性逻辑直接写在主代码中,避免了不必要的函数嵌套。
1. 原始代码完整解析
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>二维数组排序</title>
<script>
window.onload = function() {
// 1. 定义二维数组(每行代表一个一维数组)
var v = [[11,22,6],[43,12,11],[55,22,0]];
// 2. 遍历二维数组的每一行,调用排序函数(核心:仅封装单个数组排序)
for(var i of v){
// disiti函数:专门处理“单个一维数组排序”,不掺杂其他逻辑
disiti(i);
}
// 3. 遍历二维数组,输出排序后的结果(流程性逻辑,不封装)
for(var i of v){
for(var j of i){
document.write(j + " ");
}
document.write(" <br>");
}
}
// 4. 部分封装:仅负责“单个一维数组的冒泡排序”(从小到大)
function disiti(a){
// 冒泡排序核心逻辑:对传入的一维数组a排序
for(var i=0; i<a.length; i++){
for(var j=0; j<i; j++){
// 排序规则:从小到大(a[i] < a[j]时交换)
if(a[i] < a[j]){
var temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
</script>
</head>
<body>
</body>
</html>
2. 你的代码优势:精准封装,拒绝冗余
你的实现精准避开了“全流程封装”的坑,核心优势有3点:
- 封装边界清晰:只把“单个一维数组排序”这个“可复用核心逻辑”封装成
disiti
函数,二维数组的遍历(for(var i of v)
)、结果输出(document.write
)等“一次性流程”直接写在主逻辑中,不用额外套函数。 - 修改成本极低:如果要切换“从小到大”→“从大到小”排序,只需改
disiti
函数里的if(a[i] < a[j])
为if(a[i] > a[j])
,不用动其他代码;如果用“全流程封装”,要么加参数判断(比如sortType: 'asc'/'desc'
),要么写两个函数,代码量翻倍。 - 可读性更高:主逻辑按“定义数组→排序→输出”的顺序线性执行,没有多层函数嵌套,新手也能一眼看懂流程;而全流程封装会把这些步骤藏在函数内部,反而增加理解成本。
二、为什么“全流程封装”反而不划算?
我们来对比“你的部分封装”和“全流程封装”的差异,就能明白为什么前者更优。
1. 全流程封装的典型写法(反例)
假设有人把“二维数组遍历+单个数组排序+结果输出”全封装成一个函数,代码会变成这样:
// 全流程封装:包含二维数组排序+输出,看似“统一”,实则冗余
function sort2DArrayAndPrint(twoDArray, sortType = 'asc') {
// 步骤1:遍历二维数组,排序每一行(内置排序逻辑,不独立)
for(let arr of twoDArray) {
// 冒泡排序逻辑写在内部,无法单独复用
for(let i=0; i<arr.length; i++) {
for(let j=0; j<i; j++) {
// 步骤2:处理排序方向,增加判断逻辑
if(sortType === 'asc' ? arr[i] < arr[j] : arr[i] > arr[j]) {
[arr[i], arr[j]] = [arr[j], arr[i]]; // 解构赋值交换
}
}
}
}
// 步骤3:输出结果(内置输出逻辑,无法单独复用)
for(let arr of twoDArray) {
for(let item of arr) {
document.write(item + " ");
}
document.write(" <br>");
}
}
// 调用时
window.onload = function() {
var v = [[11,22,6],[43,12,11],[55,22,0]];
sort2DArrayAndPrint(v, 'asc'); // 从小到大
// sort2DArrayAndPrint(v, 'desc'); // 从大到小
}
2. 全流程封装的问题:3个“不灵活”
- 逻辑耦合严重:排序逻辑、输出逻辑、二维数组遍历逻辑全揉在一个函数里,若只想“排序但不输出”(比如后续要把结果用表格展示,不用
document.write
),必须修改函数内部,违反“单一职责原则”。 - 修改成本更高:若要更换排序算法(比如冒泡→快速排序),需要在函数内部大改;若要支持更多排序方向(比如按数组最后一个元素排序),会增加更多
if-else
判断,代码越来越臃肿。 - 复用性反而低:
sort2DArrayAndPrint
函数只能处理“二维数组排序+输出”的固定场景,无法单独复用“单个数组排序”逻辑(比如其他地方有个一维数组要排序,只能重写代码)。
三、你的代码优化:保留核心优势,提升易用性
你的“部分封装”思路完全正确,我们可以在此基础上做些细节优化,让代码更规范、灵活,同时不增加冗余。
优化方向:
- 命名语义化:
v
→twoDArray
(明确是二维数组),disiti
→sortSingleArray
(明确是“排序单个数组”),避免拼音命名,增强可读性。 - 支持排序方向:给
sortSingleArray
加一个sortType
参数,支持“从小到大(asc)”和“从大到小(desc)”,不用写两个函数。 - 优化输出方式:用
div
+span
替代document.write
,让输出格式更规整,便于样式控制。 - 添加注释:给核心逻辑加注释,方便后续维护。
优化后完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>优化版二维数组排序</title>
<style>
/* 优化输出样式:用网格布局让结果更规整 */
.result-container {
margin: 20px;
padding: 15px;
border: 1px solid #eee;
border-radius: 6px;
}
.array-row {
margin: 8px 0;
padding: 8px;
background-color: #f8f9fa;
}
.array-item {
display: inline-block;
width: 40px;
text-align: center;
margin: 0 4px;
padding: 4px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="result-container" id="resultContainer"></div>
<script>
window.onload = function() {
// 1. 定义二维数组(命名语义化:twoDArray = 二维数组)
const twoDArray = [[11, 22, 6], [43, 12, 11], [55, 22, 0]];
// 2. 遍历二维数组,调用排序函数(支持指定排序方向:'asc'升序,'desc'降序)
// 需求1:从小到大排序 → 传'sortSingleArray(arr, 'asc')'
// 需求2:从大到小排序 → 传'sortSingleArray(arr, 'desc')'
for (const arr of twoDArray) {
sortSingleArray(arr, 'asc'); // 核心:复用排序函数,仅改参数即可切换方向
}
// 3. 输出排序结果(优化为DOM操作,替代document.write,便于样式控制)
renderResult(twoDArray);
}
/**
* 部分封装:仅负责“单个一维数组的冒泡排序”(支持升序/降序)
* @param {Array} arr - 要排序的一维数组
* @param {string} sortType - 排序方向:'asc'(升序,默认)、'desc'(降序)
*/
function sortSingleArray(arr, sortType = 'asc') {
// 冒泡排序核心逻辑(仅处理单个一维数组)
const len = arr.length;
for (let i = 0; i < len; i++) {
// 内层循环:比较相邻元素,完成一轮排序
for (let j = 0; j < i; j++) {
// 根据sortType判断排序规则(仅此处需要修改,其他逻辑不变)
const needSwap = sortType === 'asc'
? arr[i] < arr[j] // 升序:后面的元素小则交换
: arr[i] > arr[j]; // 降序:后面的元素大则交换
if (needSwap) {
// 交换元素(ES6解构赋值,更简洁)
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
}
/**
* 辅助函数:渲染二维数组排序结果(流程性逻辑,单独封装便于修改样式)
* @param {Array} twoDArray - 排序后的二维数组
*/
function renderResult(twoDArray) {
const container = document.getElementById('resultContainer');
container.innerHTML = '<h3>二维数组排序结果(升序)</h3>';
// 遍历二维数组,创建DOM元素展示
twoDArray.forEach((arr, rowIndex) => {
const rowDiv = document.createElement('div');
rowDiv.className = 'array-row';
rowDiv.innerHTML = `<span>第${rowIndex + 1}行:</span>`;
// 遍历一维数组,创建每个元素的展示节点
arr.forEach(item => {
const itemSpan = document.createElement('span');
itemSpan.className = 'array-item';
itemSpan.textContent = item;
rowDiv.appendChild(itemSpan);
});
container.appendChild(rowDiv);
});
}
</script>
</body>
</html>
优化后保留的核心优势:
- 封装依然精简:
sortSingleArray
只负责“单个数组排序”,renderResult
只负责“结果渲染”,两个函数各司其职,没有冗余逻辑。 - 修改依然灵活:
- 切换排序方向:只需改
sortSingleArray(arr, 'asc')
→sortSingleArray(arr, 'desc')
; - 修改输出样式:只需调整
renderResult
里的CSS类或DOM结构,不用动排序逻辑; - 更换排序算法:只需重写
sortSingleArray
内部的排序逻辑(比如改成快速排序),主逻辑和渲染逻辑完全不变。
- 切换排序方向:只需改
四、总结:按需封装的3个核心原则
从你的二维数组排序代码中,我们可以提炼出“按需封装”的实用原则——封装的目的是“减少重复代码”,不是“为了封装而封装”:
1. 只封装“可复用的核心逻辑”
- 可复用逻辑:比如“单个数组排序”(其他场景可能也需要排序一维数组)、“日期格式化”(多个地方需要展示日期);
- 不封装“一次性流程”:比如“当前页面的二维数组遍历”“特定格式的结果输出”(这些逻辑只在当前场景用,封装后反而增加调用成本)。
2. 封装函数保持“单一职责”
- 一个函数只做一件事:
sortSingleArray
只排序,renderResult
只渲染,不要让一个函数既排序又输出,既处理数组又判断逻辑; - 示例:如果要给二维数组排序加“按某一列排序”(比如按每行第二个元素排序),只需新增一个
sort2DArrayByColumn
函数,复用sortSingleArray
,而不是在原有函数里加if-else
。
3. 用“参数”替代“多函数”
- 需求差异用参数控制:比如排序方向(
sortType: 'asc'/'desc'
)、数组列索引(columnIndex: 0/1/2
),不用为每个需求写一个函数; - 反例:不要写
sort2DArrayAsc
(升序)、sort2DArrayDesc
(降序)两个函数,而是用一个sort2DArray
加参数控制,减少代码重复。
最终结论
你的二维数组排序实现思路完全正确——部分封装核心逻辑,灵活处理流程性需求,比全流程封装更能减少代码量、提升灵活性。在实际开发中,我们不需要追求“大而全”的封装,而是要像你这样,根据需求精准判断“哪些该封装,哪些该直接写”,这才是高效的编码思路。