【前端教程】二维数组排序实战:按需封装才是最优解——拒绝冗余,精简代码

发布于:2025-08-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

在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 + "&nbsp;&nbsp;&nbsp;");
    }
    document.write("&nbsp;&nbsp;&nbsp;<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 + "&nbsp;&nbsp;&nbsp;");
    }
    document.write("&nbsp;&nbsp;&nbsp;<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函数只能处理“二维数组排序+输出”的固定场景,无法单独复用“单个数组排序”逻辑(比如其他地方有个一维数组要排序,只能重写代码)。

三、你的代码优化:保留核心优势,提升易用性

你的“部分封装”思路完全正确,我们可以在此基础上做些细节优化,让代码更规范、灵活,同时不增加冗余。

优化方向:

  1. 命名语义化vtwoDArray(明确是二维数组),disitisortSingleArray(明确是“排序单个数组”),避免拼音命名,增强可读性。
  2. 支持排序方向:给sortSingleArray加一个sortType参数,支持“从小到大(asc)”和“从大到小(desc)”,不用写两个函数。
  3. 优化输出方式:用div+span替代document.write,让输出格式更规整,便于样式控制。
  4. 添加注释:给核心逻辑加注释,方便后续维护。

优化后完整代码

<!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加参数控制,减少代码重复。

最终结论

你的二维数组排序实现思路完全正确——部分封装核心逻辑,灵活处理流程性需求,比全流程封装更能减少代码量、提升灵活性。在实际开发中,我们不需要追求“大而全”的封装,而是要像你这样,根据需求精准判断“哪些该封装,哪些该直接写”,这才是高效的编码思路。


网站公告

今日签到

点亮在社区的每一天
去签到