WPS JS宏编程教程(从基础到进阶)-- 第七部分:JS对象在WPS中的应用

发布于:2025-04-20 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

所有章节教程word文件可以点击如下链接获取
wps-excel办公+JS宏编程教程基础到进阶+函数使用手册

第7章 JS对象在WPS中的应用

7-1 对象创建的几种方法

从零理解对象:数据收纳盒

想象一下,对象就像是一个多层的收纳盒,每个抽屉(属性)可以存放不同类型的东西——数字、文本、公式,甚至另一个收纳盒(嵌套对象)。在WPS表格中,用对象管理数据能让代码更清晰。

两种基础创建方式
  1. 空盒子起手
    new Object()或字面量{}创建一个空对象,适合逐步填充数据:

    // 方法1:构造函数
    var student = new Object();
    
    // 方法2:字面量(更常用)
    var product = {};
    
  2. 预装数据的盒子
    初始化时直接定义属性,适合固定结构的数据:

    var order = {
      id: "202309001",       // 字符串
      items: ["笔记本", "鼠标"], // 数组
      total: 599.99,         // 数字
      isValid: true,         // 布尔值
      getDiscount: function() {  // 函数
        return this.total * 0.9;
      }
    };
    
代码解析表
行号 代码片段 作用说明 输入/输出示例
2 var student = new Object(); 创建空对象 student初始为空
5 id: "202309001" 添加字符串类型属性 order.id返回"202309001"
6 items: ["笔记本", "鼠标"] 添加数组类型属性 order.items[0]返回"笔记本"
9 getDiscount: function() 定义方法:计算9折价格 order.getDiscount()返回539.99

7-2 对象属性的查、改、增、删

像操作Excel单元格一样管理属性

对象属性操作类似表格中的单元格——可随时读取、修改、删除。关键在于灵活使用两种操作符:

1. 点操作符(静态键名)
  • 适合已知属性名:直接通过.访问
    // 读取
    console.log(order.id);      // 输出"202309001"
    
    // 修改
    order.total = 699.99;       // 总价更新为699.99
    
    // 删除
    delete order.isValid;       // 移除是否有效标识
    
2. 中括号操作符(动态键名)
  • 适合变量或特殊字符
    var key = "total";
    console.log(order[key]);    // 输出699.99
    
    // 添加带空格的属性
    order["delivery address"] = "北京市朝阳区"; 
    
动态属性应用场景

假设需要根据单元格A1的值动态读取属性:

var cellValue = Range("A1").Value();
var data = {
  January: 1500,
  February: 2300
};
console.log(data[cellValue]);  // 若A1是"February",输出2300

7-3 循环对象中的属性

批量操作:像筛选行一样遍历对象

当需要处理对象中的所有属性时,三种方法轻松实现遍历:

方法对比表
方法 作用 示例 输出示例
Object.keys(obj) 获取所有属性名 Object.keys(order) ["id", "items", "total", ...]
Object.values(obj) 获取所有属性值 Object.values(order) ["202309001", Array, 699.99, ...]
Object.entries(obj) 获取键值对数组 Object.entries(order) [["id", "202309001"], ["total", 699.99], ...]
实战:将对象写入Excel
var sales = {
  产品A: 1200,
  产品B: 980,
  产品C: 1560
};

// 写入A列(产品名)和B列(销售额)
var row = 1;
Object.entries(sales).forEach(([product, amount]) => {
  Cells(row, 1).Value2 = product;  // A列
  Cells(row, 2).Value2 = amount;  // B列
  row++;
});

7-4 实例1:提取各项目最后1条记录

场景:抓取最新数据

假设有一个不断更新的销售记录表,需要提取每个客户的最后一次交易数据。对象可以像“实时监控器”一样,始终保留最新值。

原始数据示例
姓名 销售额 时间
张三 1500 2023-09-01
李四 800 2023-09-02
张三 2000 2023-09-03
王五 1200 2023-09-04
代码实现
function 提取最后记录() {
  var data = Range("A2:C5").Value();  // 读取原始数据
  var latestData = {};                // 空对象存储最新记录
  
  // 遍历每一行数据
  data.forEach(row => {
    var name = row[0];               // 提取姓名(A列)
    latestData[name] = row.slice(1); // 更新为当前行的数据
  });

  // 将对象转为数组并写入新区域
  var output = Object.entries(latestData).map(([key, val]) => [key, ...val]);
  Range("E2").Resize(output.length, 3).Value2 = output;
}
代码解析表
行号 代码片段 作用说明 数据变化示例
2 var data = Range("A2:C5") 读取A2:C5区域数据 数据格式:二维数组
5 latestData[name] = row.slice(1) 用姓名作为键,覆盖存储最新数据 张三最终对应[2000, "2023-09-03"]
9 Object.entries(latestData) 将对象转为键值对数组 [ ["张三", [2000, "2023-09-03"]], ... ]
10 Range("E2").Resize(...) 将结果写入E2开始的区域 E列显示姓名,F列销售额,G列时间
输出结果
E F G
张三 2000 2023-09-03
李四 800 2023-09-02
王五 1200 2023-09-04

7-5 实例2:提取各项目第1条记录

场景:记录首次出现的数据

需要统计每个客户的首次交易信息,类似在Excel中标记“首次出现”的筛选功能。

原始数据(同7-4)
代码实现
function 提取第一条记录() {
  var data = Range("A2:C5").Value();
  var firstData = {};                 // 存储首次记录
  var counter = 1;                    // 序号生成器

  data.forEach(row => {
    var name = row[0];
    if (!firstData[name]) {           // 仅当不存在时存储
      firstData[name] = [counter++, ...row.slice(1)]; // 添加序号
    }
  });

  // 转换并写入结果
  var output = Object.values(firstData);
  Range("E2").Resize(output.length, 4).Value2 = output;
}
代码解析表
行号 代码片段 作用说明 关键逻辑
5 if (!firstData[name]) 检查是否已存在该姓名的记录 李四首次出现时条件为true
6 counter++ 自增序号生成 生成唯一序号:1,2,3…
6 ...row.slice(1) 展开数组元素 [1500, "2023-09-01"]转为独立元素
输出结果
E F G H
1 张三 1500 2023-09-01
2 李四 800 2023-09-02
3 王五 1200 2023-09-04

7-6 实例3:按指定字段汇总数据

场景:快速统计分组总和

类似Excel的“SUMIF”函数,但用对象实现动态聚合。

原始数据(同7-4)
代码实现
function 按姓名汇总() {
  var data = Range("A2:C5").Value();
  var sumData = {};  // 用对象存储累加结果

  data.forEach(row => {
    var name = row[0];
    var amount = row[1];
    // 累加逻辑:如果存在则加,否则初始化
    sumData[name] = (sumData[name] || 0) + amount;
  });

  // 转为二维数组并写入
  var output = Object.entries(sumData);
  Range("E2").Resize(output.length, 2).Value2 = output;
}
代码解析表
行号 代码片段 作用说明 数学过程示例
6 sumData[name] = (...) 累加逻辑 张三:1500 → 1500+2000=3500
9 Object.entries(sumData) 转换为[["张三", 3500], ...] 直接适配单元格写入格式
输出结果
E F
张三 3500
李四 800
王五 1200

7-7 实例4:按指定字段做多种汇总

场景:多维数据统计

需要同时计算每个产品的销售额总和、最大值和平均值,类似Excel的数据透视表功能。

原始数据(扩展版)
产品 销售额 月份
手机 12000 1月
笔记本 9800 1月
手机 15000 2月
耳机 3000 2月
笔记本 11500 3月
代码实现
function 多维统计() {
  var data = Range("A2:C6").Value();
  var statData = {};  // 存储统计数据的对象
  
  data.forEach(row => {
    var product = row[0];
    var amount = row[1];
    
    // 初始化或更新统计数据
    if (!statData[product]) {
      statData[product] = {
        total: 0,
        max: -Infinity,
        count: 0
      };
    }
    
    // 更新统计值
    statData[product].total += amount;
    statData[product].max = Math.max(statData[product].max, amount);
    statData[product].count++;
  });

  // 生成输出数组
  var output = Object.entries(statData).map(([product, data]) => [
    product,
    data.total,
    data.max,
    data.total / data.count
  ]);
  
  // 写入结果
  Range("E2").Resize(output.length, 4).Value2 = output;
}
代码解析表
行号 代码片段 作用说明 数据结构示例
4 var statData = {} 初始化空对象存储统计结果 { 手机: {total:0, max:0, count:0} }
9-15 if (!statData[product]) 初始化新产品的统计容器 首次遇到"耳机"时创建新键
18 statData[product].total += 累加销售额 手机:12000+15000=27000
24 Object.entries(statData) 将对象转为[["手机", {...}], ...] 便于映射为表格数据
输出结果
E F G H
手机 27000 15000 13500
笔记本 21300 11500 10650
耳机 3000 3000 3000

7-8 实例5:按多字段做多种汇总

场景:复合条件分析

需要按"产品+月份"组合统计数据,类似Excel中的多级分类汇总。

原始数据(同7-7)
代码实现
function 复合键统计() {
  var data = Range("A2:C6").Value();
  var statData = {};  // 存储复合键统计结果
  
  data.forEach(row => {
    var product = row[0];
    var month = row[2];
    var amount = row[1];
    var key = product + "|" + month;  // 构建复合键
    
    // 初始化或更新统计
    if (!statData[key]) {
      statData[key] = {
        total: 0,
        transactions: []
      };
    }
    statData[key].total += amount;
    statData[key].transactions.push(amount);
  });

  // 生成输出数组
  var output = Object.entries(statData).map(([key, data]) => {
    var [product, month] = key.split("|");
    return [
      product,
      month,
      data.total,
      Math.max(...data.transactions),
      data.transactions.length
    ];
  });
  
  // 写入结果
  Range("E2").Resize(output.length, 5).Value2 = output;
}
代码解析表
行号 代码片段 作用说明 关键技巧
6 `var key = product + " " + month` 构建复合键
15 transactions.push(amount) 存储明细数据 保留原始交易记录供后续分析
21 `key.split(" ")` 拆分复合键
输出结果
E F G H I
手机 1月 12000 12000 1
笔记本 1月 9800 9800 1
手机 2月 15000 15000 1
耳机 2月 3000 3000 1
笔记本 3月 11500 11500 1

7-9 实例6:按条件读取多表再拆成多表

场景:跨表数据整合

从多个工作表中提取数据,按指定条件合并后重新拆分到新表。

原始数据(假设有3个月份表)

1月表

产品 销售额
手机 12000
笔记本 9800

2月表

产品 销售额
手机 15000
耳机 3000
代码实现
function 跨表合并拆分() {
  var mergedData = {};  // 存储合并后的数据
  var sheets = ["1月", "2月", "3月"];  // 目标工作表名
  
  sheets.forEach(sheetName => {
    var sheet = Sheets(sheetName);
    var data = sheet.Range("A2:B" + sheet.Range("A1").End(xlDown).Row).Value();
    
    data.forEach(row => {
      var product = row[0];
      if (!mergedData[product]) {
        mergedData[product] = [];
      }
      mergedData[product].push([sheetName, row[1]]);  // 添加月份数据
    });
  });

  // 创建新工作簿写入数据
  var newWb = Workbooks.Add();
  Object.entries(mergedData).forEach(([product, data]) => {
    var ws = newWb.Sheets.Add();
    ws.Name = product;
    ws.Range("A1").Value2 = ["月份", "销售额"];
    ws.Range("A2").Resize(data.length, 2).Value2 = data;
  });
}
代码解析表
行号 代码片段 作用说明 动态特性
6 sheet.Range("A1").End(xlDown) 动态获取数据末尾行 适应不同月份数据量变化
11 mergedData[product].push(...) 按产品聚合各月数据 手机数据包含1月、2月记录
17 newWb.Sheets.Add() 动态创建工作表 每个产品独立工作表
输出效果

手机表

月份 销售额
1月 12000
2月 15000

耳机表

月份 销售额
2月 3000

7-10 实例7:拆分单表到多工作簿下的多表

场景:数据分库存储

将一张包含全国各城市销售数据的总表,按省份拆分为独立工作簿(每个省份一个文件),适合数据分发场景。

原始数据
城市 省份 销售额
杭州市 浙江 15000
温州市 浙江 9800
南京市 江苏 22000
苏州市 江苏 16500
广州市 广东 31800
代码实现
function 按省份拆分工作簿() {
  var mainData = Range("A2:C6").Value();  // 读取源数据
  var provinceData = {};                  // 按省份分组存储
  
  // 数据分组
  mainData.forEach(row => {
    var province = row[1];
    if (!provinceData[province]) {
      provinceData[province] = [];
    }
    provinceData[province].push(row);
  });

  // 创建独立工作簿
  Object.entries(provinceData).forEach(([province, data]) => {
    var newWb = Workbooks.Add();          // 新建工作簿
    var ws = newWb.ActiveSheet;
    ws.Name = province + "销售数据";
    
    // 写入表头和内容
    ws.Range("A1:C1").Value2 = [["城市", "省份", "销售额"]];
    ws.Range("A2").Resize(data.length, 3).Value2 = data;
    
    // 保存文件
    var path = ThisWorkbook.Path + "\\" + province + ".xlsx";
    newWb.SaveAs(path);
    newWb.Close();
  });
}
代码解析表
行号 代码片段 作用说明 关键逻辑
5 var province = row[1] 提取省份字段(B列) 浙江/江苏/广东作为分组依据
8 provinceData[province].push 按省份归类数据 所有浙江数据存入同一个数组
15 newWb = Workbooks.Add() 为每个省份创建独立工作簿 内存中生成新文件
20 path = ThisWorkbook.Path... 生成保存路径 文件保存在原文档相同目录
生成文件示例

浙江.xlsx内容:

A B C
城市 省份 销售额
杭州 浙江 15000
温州 浙江 9800

7-11 对象的属性值为函数用法

场景:封装工具方法

将常用功能(如格式设置、计算逻辑)封装为对象方法,实现代码复用。

案例:颜色管理器
// 定义工具对象
var formatTool = {
  // 设置单元格背景色(输入范围对象,输出无)
  setColor: function(rng, colorIndex) {
    rng.Interior.ColorIndex = colorIndex;
  },
  
  // 生成随机颜色值(输入无,输出数字)
  randomColor: function() {
    return Math.floor(Math.random() * 56) + 1;
  }
};

// 应用示例
function 随机着色() {
  var dataRng = Range("A2:C6");
  formatTool.setColor(dataRng, formatTool.randomColor());
}
方法说明表
方法名 输入参数 输出类型 示例调用
setColor Range对象, 颜色索引值 setColor(Range("A1"), 3)
randomColor 数字 var c = randomColor() → 生成5
执行效果
  • A2:C6区域背景色变为随机索引色(1-56)

7-12 用构造函数自定义类-1

场景:标准化数据处理

创建可复用的数据处理器类,统一管理同类操作。

订单处理类
// 定义构造函数
function OrderProcessor(sheetName) {
  // 属性初始化
  this.sheet = Sheets(sheetName);
  this.dataRange = this.sheet.UsedRange;
  
  // 方法:获取总销售额
  this.getTotalSales = function() {
    return this.dataRange.Columns(3).Value().flat().reduce((a,b) => a + b);
  };
}

// 使用示例
function 处理订单() {
  var processor = new OrderProcessor("订单表");  // 实例化对象
  console.log("总销售额:" + processor.getTotalSales());
}
类结构解析表
组件 代码片段 作用说明
构造函数 function OrderProcessor() 接收表名参数并初始化
属性 this.sheet 存储工作表对象
方法 this.getTotalSales 计算第三列总和
输入输出示例
  • 输入:new OrderProcessor("订单表")
  • 输出方法:getTotalSales()返回数值如150000

7-13 用构造函数自定义类-2

场景:动态数据容器

构建支持动态分析的数据模型,适合实时计算场景。

销售分析器类
function SalesAnalyzer(dataRange) {
  // 初始化数据
  this.rawData = dataRange.Value();
  
  // 方法:按产品过滤
  this.filterByProduct = function(product) {
    return this.rawData.filter(row => row[0] === product);
  };
  
  // 方法:计算平均值
  this.getAverage = function() {
    var values = this.rawData.map(row => row[1]);
    return values.reduce((a,b) => a + b) / values.length;
  };
}

// 使用示例
function 分析数据() {
  var analyzer = new SalesAnalyzer(Range("A2:B10"));
  var phoneData = analyzer.filterByProduct("手机");
  console.log("手机平均销售额:" + analyzer.getAverage());
}
方法参数说明
方法名 输入类型 输出类型 示例
filterByProduct 字符串(产品名) 二维数组 filterByProduct("手机")
getAverage 数字 输出如13500


7-14 用class构造自定义类-3

场景:现代化代码封装

ES6的class语法让面向对象编程更直观,适合构建复杂工具。我们将重构7-13节的销售分析器,体验现代语法优势。

类定义:销售统计器
class SalesAnalyzer {
  constructor(dataRange) {
    // 初始化数据源
    this.rawData = dataRange.Value();
  }

  // 方法1:按产品过滤数据
  filterByProduct(product) {
    return this.rawData.filter(row => row[0] === product);
  }

  // 方法2:计算平均值(保留两位小数)
  getAverage() {
    const sum = this.rawData
      .map(row => row[1])
      .reduce((a, b) => a + b);
    return Number(sum / this.rawData.length).toFixed(2);
  }

  // 方法3:生成统计报告
  generateReport() {
    return `${this.rawData.length}条数据,平均销售额:${this.getAverage()}`;
  }
}
代码解析表
代码片段 作用说明 与传统构造函数对比优势
class SalesAnalyzer 声明类 结构更清晰,接近Java/C#语法
constructor(dataRange) 初始化方法 替代function SalesAnalyzer()
filterByProduct() 类方法定义 无需this.前缀,直接写函数名
toFixed(2) 保留两位小数 避免浮点数精度问题
使用示例
function 生成分析报告() {
  const analyzer = new SalesAnalyzer(Range("A2:B10"));  // 输入数据区域
  console.log(analyzer.generateReport());               // 输出:共9条数据,平均销售额:13500.00
}

7-15 实例8:自定义关于[唯一性]的类

场景:数据去重与可视化

需要为表格中的唯一值自动着色(如分类标识),并通过类封装实现复用。

类设计:唯一值处理器
class UniqueValueProcessor {
  constructor(targetRange) {
    this.range = targetRange;              // 存储目标区域
    this.uniqueValues = [];                // 存储唯一值列表
  }

  // 方法1:提取唯一值
  extractUniqueValues() {
    const allValues = this.range.Value().flat();
    this.uniqueValues = [...new Set(allValues)];  // Set去重
  }

  // 方法2:为唯一值随机着色
  colorizeUniqueValues() {
    this.uniqueValues.forEach(value => {
      const color = Math.floor(Math.random() * 56) + 1;  // 1-56号颜色
      this.range.Cells.Find(value).Interior.ColorIndex = color;
    });
  }
}
使用演示
function 处理唯一值() {
  const processor = new UniqueValueProcessor(Range("B2:B10"));  // 输入分类列
  processor.extractUniqueValues();   // 提取唯一值
  processor.colorizeUniqueValues();  // 着色单元格
}
输入输出示例
  • 输入数据(B列):

    分类
    电子产品
    服装
    食品
    电子产品
  • 执行结果

    • uniqueValues数组:["电子产品", "服装", "食品"]
    • B列中每个分类显示不同背景色

7-16 实例8: [唯一性]类的应用

场景:跨表数据整合

将多个工作表中相同列的唯一值汇总,并生成全局统计报告。

扩展应用:跨表处理器
class CrossSheetUniqueProcessor {
  constructor(sheetNames, columnIndex) {
    this.sheets = sheetNames;    // 工作表名数组
    this.column = columnIndex;   // 要处理的列号(从1开始)
    this.globalUniqueSet = new Set();  // 存储全局唯一值
  }

  // 方法:遍历所有表提取唯一值
  collectAllUniqueValues() {
    this.sheets.forEach(sheetName => {
      const sheet = Sheets(sheetName);
      const range = sheet.Columns(this.column);
      const values = range.Value().flat();
      values.forEach(v => this.globalUniqueSet.add(v));
    });
  }

  // 方法:生成汇总报告
  generateReport() {
    const reportSheet = Sheets.Add();
    reportSheet.Name = "唯一值汇总";
    const uniqueArray = [...this.globalUniqueSet];
    reportSheet.Range("A1").Resize(uniqueArray.length, 1).Value2 = uniqueArray;
  }
}
使用示例
function 跨表汇总() {
  const processor = new CrossSheetUniqueProcessor(["1月", "2月", "3月"], 2);  // 处理各表B列
  processor.collectAllUniqueValues();  // 收集数据
  processor.generateReport();          // 生成汇总表
}
输入输出示例
  • 输入表结构(各月表B列):

    1月表 2月表
    笔记本电脑 智能手机
    智能手机 耳机
  • 输出结果(汇总表A列):

    唯一值汇总
    笔记本电脑
    智能手机
    耳机

本章小结

通过对象和类,我们实现了:

  1. 数据封装:将复杂操作隐藏在类内部
  2. 代码复用:通过实例化重复使用逻辑
  3. 可维护性:修改类定义即可影响所有调用处

尝试用面向对象思维设计你的下一个WPS宏项目吧!


网站公告

今日签到

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