阅读视图

发现新文章,点击刷新页面。

初识XPath——了解XML与HTML文档中的导航神器

引言

在Web开发和自动化测试中,常常需要定位和操作页面中的元素。传统上,我们用CSS选择器,但在某些复杂场景下,XPath是一种更强大、更灵活的工具。本文将带你由浅入深,了解XPath的基本概念和用法。


什么是XPath?

XPath(XML Path Language)是一种用于在XML文档中查找信息的语言。由于HTML是HTML5的标准变体,可视为XML的一种,只要遵守标准,同样适用XPath。

XPath的用途

  • 选择特定元素或一组元素
  • 计算元素的路径
  • 提取元素的内容或属性
  • 在自动化测试框架(如Selenium)中定位元素

XPath的基本结构

XPath表达式类似路径,用于从文档的根节点开始,逐层筛选目标。

例子

<html>
  <body>
    <div id="main">
      <h1>标题</h1>
      <p class="text">这是一段文字。</p>
    </div>
  </body>
</html>

对应的XPath:

  • 选择<h1>//h1
  • 选择<p>//p[@class='text']
  • 选择<div id="main">//div[@id='main']

常用的XPath表达式

表达式 描述 示例
/ 从根节点开始,绝对路径 /html/body/div
// 在文档中查找匹配的元素,不考虑层级 //p
. 当前节点 ./span
.. 父节点 ../div
@属性名 指定属性 //a[@href='https://']
* 任意元素 //*/a

结合条件过滤

  • [条件]:筛选出满足条件的元素
  • 例://div[@class='main']:选择class为main的div
  • 叠加过滤://ul/li[1]:第一个li元素

实战演练:用XPath定位元素

如果你安装了谷歌浏览器,可以安装Xpath测试器进行实战演练

screenshot_2025-07-02_19-02-00.png


小结

XPath是网页元素定位的重要工具,掌握其基础语法可以帮助你更高效地进行网页自动化、数据抓取与测试验证。


如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

告别盲测:Jest--JavaScript测试之道

为什么要测试,测试什么?

为什么要测试?

  • 提升代码质量和可靠性: 尽早发现并修复bug,减少线上事故。
  • 增强重构信心: 有测试用例保驾护航,你可以大胆地优化和重构代码,因为你知道它们会立即发现潜在的回归错误。
  • 提高开发效率: 避免了手动重复测试的繁琐,让你可以更快地迭代新功能。

测试什么?

我们通常将测试分为几个层次:

  • 单元测试 (Unit Tests): 针对代码中最小的可独立测试单元进行测试,如单个函数、类的方法。它们应该快速、独立、可重复。这是JTest的基础和核心。

  • 集成测试 (Integration Tests): 测试多个单元或模块协同工作时的行为,验证它们之间的接口和交互是否正确。例如,测试一个UI组件与数据层API的交互。


JTest初体验——告别盲测的第一步 (Jest入门)

环境搭建

首先,我们来安装Jest。这是一个零配置的测试框架,非常适合快速上手。

# 进入你的项目目录
cd your-js-project

# 安装 Jest
npm install --save-dev jest

然后,在 package.json 中添加一个 test 脚本:

{
  "name": "your-js-project",
  "version": "1.0.0",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^30.0.3"
  }
}

2.2 你的第一个JTest用例

让我们从一个最简单的纯函数开始:加法。

src/sum.js

// 这是一个简单的加法函数
function sum(a, b) {
  return a + b;
}

module.exports = sum; // 导出函数

接下来,我们为 sum.js 创建一个测试文件。根据Jest的约定,测试文件通常与源文件放在同一目录下,并以 .test.js.spec.js 结尾。

src/sum.test.js

const sum = require('./sum'); // 导入要测试的函数

// describe 块用于组织相关的测试用例
describe('sum 函数', () => {
    // test (或 it) 定义一个具体的测试用例
    test('应该正确计算两个正数的和', () => {
        // expect(value) 是 Jest 的全局函数,用于声明一个断言
        // .toBe(expected) 是一个匹配器 (matcher),用于比较值是否相等
        expect(sum(1, 2)).toBe(3);
    });

    test('应该正确计算一个正数和一个负数的和', () => {
        expect(sum(5, -3)).toBe(2);
    });

    test('应该正确计算两个零的和', () => {
        expect(sum(0, 0)).toBe(0);
    });
});

2.3 运行测试

现在,打开你的终端,运行测试命令:

npm test

你将看到类似以下的输出:

> jest

 PASS  ./sum.test.js
  sum 函数
     应该正确计算两个正数的和 (2 ms)
     应该正确计算一个正数和一个负数的和
     应该正确计算两个零的和

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.387 s, estimated 1 s
Ran all test suites.

恭喜你!你已经成功编写并运行了你的第一个JTest用例。PASS 意味着你的代码通过了测试,一切正常。


深入浅出:JTest核心断言与组织

更多的断言匹配器 (Matchers)

Jest提供了丰富的匹配器,用于检查各种条件。

匹配器 描述 示例
toBe(value) 严格相等 (===),用于基本类型 expect(1).toBe(1);
toEqual(value) 递归比较对象或数组的内容相等,用于引用类型 expect({a:1}).toEqual({a:1});
not.toBe(value) 不严格相等 expect(1).not.toBe(2);
toBeTruthy() 检查是否为真值 (truthy) expect(1).toBeTruthy();
toBeFalsy() 检查是否为假值 (falsy) expect(0).toBeFalsy();
toBeNull() 检查是否为null expect(null).toBeNull();
toBeUndefined() 检查是否为undefined expect(undefined).toBeUndefined();
toBeDefined() 检查是否已定义 expect(1).toBeDefined();
toBeInstanceOf(Class) 检查是否是某个类的实例 expect(new Array()).toBeInstanceOf(Array);
toContain(item) 检查数组中是否包含某个元素 expect([1, 2, 3]).toContain(2);
toMatch(regexp) 检查字符串是否匹配正则表达式 expect('hello').toMatch(/ll/);
toThrow(error?) 检查函数是否抛出错误 expect(() => { throw new Error(); }).toThrow();
resolves.toBe(value) 检查Promise是否成功解决并匹配值 await expect(Promise.resolve(1)).resolves.toBe(1);
rejects.toThrow(error?) 检查Promise是否失败并抛出错误 await expect(Promise.reject('error')).rejects.toThrow('error');

示例:src/stringUtils.js

function capitalize(str) {
  if (typeof str !== 'string' || str.length === 0) {
    throw new Error('Input must be a non-empty string.');
  }
  return str.charAt(0).toUpperCase() + str.slice(1);
}

module.exports = { capitalize };

src/stringUtils.test.js

const { capitalize } = require('./stringUtils');

describe('capitalize 函数', () => {
    test('应该将字符串的第一个字母大写', () => {
        expect(capitalize('hello')).toBe('Hello');
    });

    test('应该返回相同的大写字符串,如果第一个字母已经是大写', () => {
        expect(capitalize('World')).toBe('World');
    });

    test('应该处理单字符字符串', () => {
        expect(capitalize('a')).toBe('A');
    });

    test('应该抛出错误,如果输入不是字符串', () => {
        expect(() => capitalize(123)).toThrow('Input must be a non-empty string.');
        expect(() => capitalize(null)).toThrow('Input must be a non-empty string.');
    });

    test('应该抛出错误,如果输入是空字符串', () => {
        expect(() => capitalize('')).toThrow('Input must be a non-empty string.');
    });
});

测试生命周期函数:beforeEachafterEach

在某些场景下,你可能需要在每个测试用例运行之前或之后执行一些设置或清理工作。Jest提供了 beforeEach, afterEach, beforeAll, afterAll 等生命周期函数。

  • beforeEach(fn): 在每个 test (或 it) 运行之前执行。
  • afterEach(fn): 在每个 test (或 it) 运行之后执行。
  • beforeAll(fn): 在当前 describe 块中的所有 test 运行之前执行一次。
  • afterAll(fn): 在当前 describe 块中的所有 test 运行之后执行一次。

示例:计数器模块的测试

src/counter.js

let count = 0;

function increment() {
    count++;
}

function decrement() {
    count--;
}

function getCount() {
    return count;
}

function reset() {
    count = 0;
}

module.exports = {
    increment,
    decrement,
    getCount,
    reset
};

src/counter.test.js

const counter = require('./counter');

describe('计数器模块', () => {
    // 在每个测试用例运行前,将计数器重置为0,确保每个测试的独立性
    beforeEach(() => {
        counter.reset();
    });

    test('increment 应该使计数器加一', () => {
        counter.increment();
        expect(counter.getCount()).toBe(1);
    });

    test('decrement 应该使计数器减一', () => {
        counter.decrement();
        expect(counter.getCount()).toBe(-1);
    });

    test('连续调用 increment 应该正确累加', () => {
        counter.increment();
        counter.increment();
        expect(counter.getCount()).toBe(2);
    });

    test('reset 应该将计数器重置为0', () => {
        counter.increment();
        counter.increment();
        counter.reset();
        expect(counter.getCount()).toBe(0);
    });
});

测试覆盖率

衡量你的测试质量的一个重要指标是测试覆盖率 (Test Coverage) 。Jest内置了此功能。

package.jsontest 脚本中添加 --coverage 标志:

{
  "scripts": {
    "test": "jest --coverage"
  }
}

运行 npm test 后,你会在终端看到一个报告,也会在项目根目录生成一个 coverage 文件夹,其中包含详细的HTML报告,你可以打开 coverage/lcov-report/index.html 查看。

覆盖率指标:

  • Statements (语句): 代码中的语句有多少被执行了。
  • Branches (分支): if/else, switch, 三元表达式等分支有多少被执行了。
  • Functions (函数): 函数有多少被调用了。
  • Lines (行): 代码行有多少被执行了。

注意: 100% 覆盖率不代表代码没有bug,它只能说明你的测试执行了所有代码路径,但无法保证这些路径的逻辑都是正确的。你应该追求有意义的覆盖率,而不是盲目追求数字。


结语


如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

JavaScript forEach 方法详解:原数组会被改变吗?

1. 什么是 forEach

forEach 是JavaScript数组的一个内置方法,用于对数组中的每个元素执行指定的函数。它的基本语法如下:

array.forEach(callback(currentValue, index, array), thisArg);
  • callback: 要执行的函数,接收三个参数:当前元素的值、当前元素的索引和原数组。
  • thisArg: 可选,执行回调时使用的 this 值。

2. forEach 的基本用法

以下是一个简单的 forEach 示例:

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((number) => {
    console.log(number);
});

在这个例子中,forEach 遍历了 numbers 数组,并打印了每个元素的值。

3. forEach 是否会修改原数组?

forEach 本身并不会自动修改原数组。它只是遍历数组并执行回调函数。如果在回调函数中对数组的元素进行了修改,那么原数组的值将会被改变。

4. 示例:forEach 修改数组的情况

让我们看一个示例,展示如何在 forEach 中修改数组的元素:

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((value, index, array) => {
    array[index] = value * 2; // 修改原数组
});

console.log(numbers); // 输出: [2, 4, 6, 8, 10]

在这个例子中,我们在 forEach 的回调函数中修改了原数组的值,将每个元素乘以2。最终,numbers 数组的值被改变了。

5. 其他数组方法与 forEach 的比较

forEach 类似的数组方法还有 mapfilterreduce。这些方法的行为各不相同:

  • map: 返回一个新数组,包含对原数组每个元素调用回调函数的结果。不会修改原数组。
  • filter: 返回一个新数组,包含所有通过测试的元素。不会修改原数组。
  • reduce: 返回一个单一值,累积所有元素的结果。不会修改原数组。

示例对比

const numbers = [1, 2, 3, 4, 5];

// 使用 map
const doubled = numbers.map((value) => value * 2);
console.log(doubled); // 输出: [2, 4, 6, 8, 10]
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原数组未修改)

// 使用 filter
const evenNumbers = numbers.filter((value) => value % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4]
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原数组未修改)

// 使用 reduce
const sum = numbers.reduce((acc, value) => acc + value, 0);
console.log(sum); // 输出: 15
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原数组未修改)

6. 总结

forEach 方法本身不会修改原数组,但如果在回调函数中对数组元素进行了修改,那么原数组的值将会被改变。在需要返回新数组的场景中,建议使用 mapfilterreduce 等方法。


如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

字符串截取大法:JavaScript 中的 6 种高效技巧,90% 开发者都不知道第 5 种!

一、基础方法:substring、substr 和 slice

1. substring() 方法

substring() 是最传统的字符串截取方法,它接受两个参数:起始索引和结束索引(可选)。

const str = "Hello, JavaScript!";

// 从索引7开始截取到末尾
console.log(str.substring(7)); // "JavaScript!"

// 从索引7截取到17(不包括17)
console.log(str.substring(7, 17)); // "JavaScript"

// 有趣特性:会自动交换参数如果start>end
console.log(str.substring(5, 3)); // "lo" (等同于 substring(3,5))
console.log(str.substring(3, 5)); // "lo"

特点

  • 不修改原字符串
  • 负参数会被当作0
  • 自动处理起始大于结束的情况

2. substr() 方法(已废弃)

虽然 substr() 仍然可用,但已被标记为遗留功能,不建议在新代码中使用。

const str = "Hello, JavaScript!";

// 从索引7开始截取10个字符
console.log(str.substr(7, 10)); // "JavaScript"

特点

  • 第二个参数是长度而非结束索引
  • 负的起始索引表示从末尾开始计算

3. slice() 方法

slice() 是最推荐的通用截取方法,行为与数组的 slice() 一致。

const str = "Hello, JavaScript!";

// 从索引7截取到末尾
console.log(str.slice(7)); // "JavaScript!"

// 从索引7截取到17(不包括17)
console.log(str.slice(7, 17)); // "JavaScript"

// 支持负索引(从末尾计算)
console.log(str.slice(-6)); // "cript!"
console.log(str.slice(7, -1)); // "JavaScript"

特点

  • 支持负索引
  • 不自动交换参数
  • 更直观和一致的行为

二、高级技巧:正则表达式截取

4. match() 方法

当需要基于模式而非位置截取时,正则表达式是强大工具。

const str = "订单号:ORD-2023-98765,请查收";

// 提取订单号
const orderNumber = str.match(/ORD-\d{4}-\d{5}/)[0];
console.log(orderNumber); // "ORD-2023-98765"

// 提取所有数字
const numbers = str.match(/\d+/g);
console.log(numbers); // ["2023", "98765"]

高级用法

  • 使用捕获组提取特定部分
  • 全局匹配获取所有符合项

5. split() + 解构赋值

这个组合技巧能优雅地提取字符串的特定部分。

const fullName = "张 三";

// 传统方式
const parts = fullName.split(" ");
const firstName = parts[0];
const lastName = parts[1];

// 使用解构赋值
const [firstName, lastName] = fullName.split(" ");
console.log(firstName, lastName); // "张" "三"

// 处理URL路径
const url = "/products/electronics/12345";
const [,, category, id] = url.split("/");
console.log(category, id); // "electronics" "12345"

优势

  • 代码简洁
  • 可读性强
  • 一次性提取多个部分

三、ES6+ 现代方法

6. 国际化截取:Intl.Segmenter

处理像中文、日文等没有空格分隔的语言时,传统方法难以正确截取词语。

const str = '测试字符串,包含中文和英文。';

// 传统方法会按字截取s
console.log(str.slice(0, 4)); // "测试字符" (可能不准确)

// 使用Intl.Segmenter按词截取
const segmenter = new Intl.Segmenter('zh-CN', { granularity: 'word' });
const segments = [...segmenter.segment(str)];
const firstWord = segments[0].segment;
console.log(firstWord); // "测试" (完整词)
console.log(segments.map((seg) => seg.segment)); // ['测试', '字符','串',   ',','包含', '中文','和',   '英文','。']

优势

  • 尊重语言规则
  • 正确处理象形文字、表情符号等
  • 支持多种粒度(字、词、句子)

四、性能优化与最佳实践

  1. 性能比较

    • 对于简单截取,slice() 通常性能最好
    • 复杂模式匹配时,预编译正则表达式更高效
  2. 安全截取

    function safeSlice(str, maxLength, suffix = "...") {
      if (str.length <= maxLength) return str;
      return str.slice(0, maxLength - suffix.length) + suffix;
    }
    
    console.log(safeSlice("这是一个很长的字符串", 8)); // "这是一个..."
    
  3. 多字节字符处理

    function mbSubstr(str, start, length) {
      return [...str].slice(start, start + length).join("");
    }
    
    console.log(mbSubstr("👨👩👧👦家庭", 1, 2)); // "👩👧"
    

结语

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

UglifyJS深度解析:从入门到精通

一、UglifyJS是什么?

UglifyJS是一个强大的JavaScript压缩和混淆工具,它能够:

  • 压缩JavaScript代码,减少文件大小
  • 优化代码性能
  • 保护代码不被轻易阅读(混淆)
  • 检测JavaScript语法错误

UglifyJS广泛应用于生产环境中的前端项目,是构建工具(如Webpack、Gulp)的重要组成部分。

二、基本使用方法

1. 安装UglifyJS

npm install uglify-js -g

2. 命令行压缩

uglifyjs input.js -o output.js

3. 基本选项

  • -c:启用压缩
  • -m:启用混淆
  • -o:指定输出文件

示例:

uglifyjs input.js -c -m -o output.js

三、配置选项详解

当配置项增多时,使用配置文件会更方便管理。创建一个 uglify.config.json 文件,使用如下命令执行

uglifyjs input.js -o output.js  --config-file uglify.config.json

1. 压缩选项

{
  "compress": {
    "drop_console": true,
    "dead_code": true,
    "unused": true
  }
}
  • drop_console: 删除console语句
  • dead_code: 删除未使用的代码
  • unused: 删除未使用的变量

2. 混淆选项

{
   "mangle": {
        "toplevel": true, 
        "eval": true, 
        "keep_fnames": false
  }
}
  • toplevel: 混淆顶层变量
  • eval: 混淆eval中的变量
  • keep_fnames: 不保留函数名

3. 输出格式

{
   "output": {
    "beautify": true,
    "comments": false
  }
}
  • beautify: 美化输出
  • comments: 不保留注释

四、高级用法

你也可以在Node.js项目中使用UglifyJS的API。以下是一个简单的模板:

const UglifyJS = require('uglify-js');
const fs = require('fs');

const code = fs.readFileSync('demo.js', 'utf-8');

const result = UglifyJS.minify(code, {
    compress: {
        drop_console: true, // 删除console.log
    },
    mangle: true, // 启用混淆
    output: {
        beautify: false, // 不美化输出
        comments: false, // 不保留注释
    },
});

fs.writeFileSync('app-minified.js', result.code);

1. 保留特定代码

{
    compress: {
        drop_console: true,
        dead_code: true
    },
    mangle: {
        toplevel: true
    },
    output: {
        comments: function(node, comment) {
            // 保留特定注释
            if (comment.value.indexOf('Some') >= 0) {
                return true;
            }
        }
    }
}

2. 处理特定函数

{
    mangle: {
        reserved: ['mySpecialFunction'] // 保留特定函数名
    }
}

3. 性能优化

{
    compress: {
        sequences: true,    // 合并多个语句
        properties: true,   // 优化属性访问
        booleans: true,     // 优化布尔表达式
        if_return: true,    // 优化if/return
        join_vars: true     // 合并变量声明
    }
}

五、总结

UglifyJS是一个强大的工具,但使用时需要注意:

  1. 合理配置压缩选项
  2. 保留必要的代码
  3. 测试压缩后的代码
  4. 考虑性能影响

结语

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

守护你的代码:JavaScript Obfuscator 实际操作指南

引言

在上篇文章中,我们深入探讨了 JavaScript 混淆的原理和意义。今天,我们将聚焦于一款在混淆领域备受推崇的利器——javascript-obfuscator!它以其强大的功能和丰富的配置选项,成为了许多开发者保护代码的得力助手。

本文将带你走进 javascript-obfuscator 的实际操作世界,从零开始,一步步学会如何使用它来提升你的 JavaScript 代码安全性,并给出一些实用的建议。准备好了吗?让我们开始吧!

准备工作:安装 javascript-obfuscator

首先,你需要将 javascript-obfuscator 安装到你的项目中。我们通常将其作为开发依赖安装。

使用 npm:

npm install javascript-obfuscator --save-dev

安装完成后,你就可以在你的项目中调用它了。

基础操作:命令行使用

javascript-obfuscator 最直接的使用方式是通过命令行。这对于构建脚本、自动化部署流程非常方便。

假设你有一个名为 my-script.js 的文件,内容如下:

// my-script.js
function sayHello(name) {
  const message = `Hello, ${name}! Welcome to my awesome app.`;
  console.log(message);
}

const userName = "Developer";
sayHello(userName);

// Some important logic here!
// var sensitiveData = 'this should be protected';

1. 基本混淆:生成混淆后的代码文件

最简单的用法就是将你的 .js 文件进行混淆,并输出到一个新的文件。

命令格式:

npx javascript-obfuscator <input_file> --output <output_file>

实操示例:

npx javascript-obfuscator my-script.js --output my-script.obfuscated.js

执行后,你会在当前目录下看到一个 my-script.obfuscated.js 文件。打开它,你会发现代码变得非常难以阅读,变量名被替换,字符串可能被加密,整体结构被重组。

输出示例:

const a0_0x4074ce = a0_0x4bc6; (function (_0x54b00d, _0x3b0184) { const _0x33237b = a0_0x4bc6, _0x3f3139 = _0x54b00d(); while (!![]) { try { const _0x103ed2 = parseInt(_0x33237b(0x1e1)) / 0x1 + parseInt(_0x33237b(0x1e4)) / 0x2 * (-parseInt(_0x33237b(0x1e8)) / 0x3) + -parseInt(_0x33237b(0x1ea)) / 0x4 + -parseInt(_0x33237b(0x1e3)) / 0x5 * (parseInt(_0x33237b(0x1eb)) / 0x6) + -parseInt(_0x33237b(0x1e7)) / 0x7 * (parseInt(_0x33237b(0x1e0)) / 0x8) + -parseInt(_0x33237b(0x1e9)) / 0x9 + parseInt(_0x33237b(0x1e5)) / 0xa * (parseInt(_0x33237b(0x1e2)) / 0xb); if (_0x103ed2 === _0x3b0184) break; else _0x3f3139['push'](_0x3f3139['shift']()); } catch (_0xc2b768) { _0x3f3139['push'](_0x3f3139['shift']()); } } }(a0_0x5c84, 0xe2898)); function a0_0x4bc6(_0x4b158a, _0x21504c) { const _0x5c84fa = a0_0x5c84(); return a0_0x4bc6 = function (_0x4bc667, _0xeef566) { _0x4bc667 = _0x4bc667 - 0x1e0; let _0x43b085 = _0x5c84fa[_0x4bc667]; return _0x43b085; }, a0_0x4bc6(_0x4b158a, _0x21504c); } function sayHello(_0xe54705) { const _0x202d82 = 'Hello,\x20' + _0xe54705 + '!\x20Welcome\x20to\x20my\x20awesome\x20app.'; console['log'](_0x202d82); } function a0_0x5c84() { const _0x3f2783 = ['14ExAAPK', '4920423yomcmx', '6376545hhdlhg', '1226148OmYpLz', '282558ycKIJX', '4454768rKIPel', '500372ZUSqtf', '8125084ygdeic', '25DvXNbT', '2enaKSl', '60hbcWcJ', 'Developer']; a0_0x5c84 = function () { return _0x3f2783; }; return a0_0x5c84(); } const userName = a0_0x4074ce(0x1e6); sayHello(userName);

关键点:

  • 默认情况下,javascript-obfuscator 会启用多种混淆技术,包括字符串数组、控制流平坦化、变量名混淆等,以提供良好的保护。

2. 控制混淆选项:定制化你的保护策略

javascript-obfuscator 提供了海量的配置选项,允许你精细控制混淆过程。你可以通过命令行参数来传递这些选项。

常用选项介绍与实操:

  • --compact: 压缩输出,移除空格和换行符。

    npx javascript-obfuscator my-script.js --output my-script.compact.js --compact true
    

    这会让输出代码更加紧凑,占用更少空间,并且也更难阅读。

  • --string-array: 将代码中的字符串收集到一个数组中,运行时再通过加密或编码的方式获取。这是非常有效的字符串保护技术。

    npx javascript-obfuscator my-script.js --output my-script.string-array.js --string-array true
    

    你会发现代码中多了一个类似 _0xXXXX 的数组,并且原始字符串不见了。

  • --string-array-encoding: 指定字符串数组的编码方式,如 base64, rc4, utf16le。RC4 通常提供更好的加密效果。

    npx javascript-obfuscator my-script.js --output my-script.rc4.js --string-array true --string-array-encoding rc4
    
  • --control-flow-flattening: 启用控制流平坦化,将代码逻辑转化为复杂的 switch 语句,极大增加理解难度。

    npx javascript-obfuscator my-script.js --output my-script.cf.js --control-flow-flattening true
    
  • --dead-code-injection: 注入无用的代码,增加代码量和阅读难度。

    npx javascript-obfuscator my-script.js --output my-script.deadcode.js --dead-code-injection true
    
  • --rename-globals: (谨慎使用!) 重命名全局变量。如果你的代码依赖于全局变量(例如被其他脚本调用),开启此选项可能导致错误。

    npx javascript-obfuscator my-script.js --output my-script.mangle-globals.js --rename-globals true
    
  • --reserved-strings: 指定一些不应被混淆的字符串。例如,把Developer列为不需要混淆的字符串。

    npx javascript-obfuscator my-script.js --output my-script.reserved.js --string-array true --reserved-strings Developer
    

3. 使用配置文件

当配置选项变得很多时,使用命令行参数会显得冗长。javascript-obfuscator 支持从配置文件读取配置,通常是一个 .json 文件。

首先,创建一个配置文件,例如 obfuscator-config.json:

{
  "compact": true,
  "controlFlowFlattening": true,
  "controlFlowFlatteningFactor": 0.75,
  "deadCodeInjection": true,
  "deadCodeInjectionPattern": "!![]",
  "stringArray": true,
  "stringArrayEncoding": ["rc4"],
  "stringArrayThreshold": 0.75,
  "renameGlobals": false,
  "splitStrings": true,
  "splitStringsChunkSize": 10,
  "unicodeEscapeSequence": true
}

然后,在命令行中使用 --config 参数:

npx javascript-obfuscator my-script.js --output my-script.config.js --config obfuscator-config.json

这样会使你的构建流程更加清晰和易于管理。

4. 使用 Node.js API

你也可以直接在你的 Node.js 脚本中调用 javascript-obfuscator 的 API。

const JavaScriptObfuscator = require('javascript-obfuscator');

const obfuscationResult = JavaScriptObfuscator.obfuscate(`
  function add(a, b) {
    return a + b;
  }

  console.log(add(5, 3));
`);

console.log(obfuscationResult.getObfuscatedCode());

结语

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

从零开始学会JavaScript混淆:安全与隐私的双重保障

引言

在前端开发中,代码的可阅读性很重要,但有时候为了保护源代码或防止被轻易篡改,我们会使用“混淆”技术,把代码变得难以理解。今天,我们就从基础开始,逐步了解JavaScript混淆的原理、方法和实用技巧。


一、什么是JavaScript混淆?

定义: 混淆(Obfuscation)是通过一系列变换,把源代码中的变量名、函数名等改成无意义的字符串,同时可能会调整代码结构,达到“让人难以理解”的目的,但程序逻辑仍然保持不变。

目的:

  • 保护代码不被轻松反编译
  • 增强代码安全性(虽然不能完全防破解)
  • 减少代码大小(部分混淆工具会压缩代码)

二、基础混淆技巧:变量重命名

简单且使用广泛的方法是将变量名重命名为无意义的字符。

原始代码示例

function add(a, b) {
  let sum = a + b;
  return sum;
}

console.log(add(2, 3));

混淆后示例

function a(b,c){let d=b+c;return d}console.log(a(2,3));

说明:

  • 函数名add变成了a
  • 参数变成bc
  • 变量sum变成d

基本技巧使用举例

// 原始
var totalPrice = 100;

function getPrice() {
  return totalPrice;
}
// 混淆后
var a=100;function b(){return a;}

总结: 简单的变量重命名可以提高代码难度,但容易被还原。


三、字符串加密(字符串“混淆”)

许多敏感字符串可以通过编码隐藏,比如用hex编码或base64

举例:简单加密字符串

原始代码

const secret = "密码123";
console.log("密钥是:" + secret);

混淆后示例(用base64编码)

const encoded = "5a+G56CBMTIz"; // "密码123"的base64编码
function decode(str) {
    return Buffer.from(str, 'base64');
}
console.log("密钥是:" + decode(encoded));

注意: 这是最基础的字符串混淆方式,实际上字符串可以用多种编码和解码方式。


四、代码结构调整:自定义函数和代码变形

为了阻碍理解,常用技巧包括:

  • 将代码拆散成多个部分
  • 使用无关紧要的变量和无用代码
  • 改变代码块的顺序(需要注意作用域)

简单实例

// 原始
function checkNumber(n) {
  if (n > 0) {
    return "正数";
  } else {
    return "非正数";
  }
}
console.log(checkNumber(5));

混淆后(伪混淆示意)

(function(){var a=5;var b=function(x){return x>0?"正数":"非正数"};console.log(b(a));})();

技巧点: 让函数调用立即执行,变量名无意义。


五、复杂混淆:多层封装和控制流扭曲

更高级的混淆会运用:

  • 代码插桩、死代码(无用代码)
  • 控制流扭曲,比如:反转逻辑、插入无意义的跳转
  • 编码字符数组,然后动态还原

示例:字符数组组合

const _0xabc = ['\x77\x61\x72\x6E', '\x74\x72\x75\x65'];
alert(_0xabc[0] + ' ' + _0xabc[1]); // 警告:"warn true"

六、使用自动化混淆工具

手工混淆很繁琐,也容易出错。建议使用成熟的工具,例如:

使用示例——UglifyJS(命令行)

uglifyjs input.js -o output.min.js -m
  • -m 表示变量名混淆(最小化)

七、混淆的注意事项

  • 性能影响: 复杂混淆可能影响加载速度
  • 调试困难: 混淆后代码难以维护
  • 反混淆工具: 市场上有反混淆工具,不能保证完全安全
  • 保护措施: 混淆只是一层保护手段,不是真正的安全保障

结语

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。


零依赖、零框架!纯JS快速掌握 JSON 转 CSV 技巧全攻略

引言

在数据处理和导出方面,JSON 和 CSV 格式是两个常见的角色。JSON(JavaScript Object Notation)以其良好的可读性和灵活性被广泛应用于数据存储和传输,而CSV(Comma-Separated Values)则因其简洁易读、方便导入到各种表格软件中而深受欢迎。今天,我们将介绍如何在纯 JavaScript 中,利用开源库 @json2csv/plainjs 轻松实现从 JSON 转换到 CSV,帮助你高效处理数据。


一、什么是 @json2csv/plainjs

@json2csv/plainjsjson2csv 的一种轻量版(纯原生 JS 实现),无需依赖任何第三方库或环境(比如 Node.js 特定模块),非常适合浏览器或纯 JS 环境下使用。

主要功能:

  • 将 JSON 对象或数组转换成 CSV 格式
  • 支持自定义字段和格式
  • 简单易用,无复杂配置

二、基础用法:将简单 JSON 转为 CSV

示例:转换一组用户数据

import { Parser } from '@json2csv/plainjs';

// 示例数据
const myData = [
    { name: 'John', age: 30, city: 'New York' },
    { name: 'Jane', age: 25, city: 'Los Angeles' },
    { name: 'Doe', age: 35, city: 'Chicago' }
];

// 基本转换示例
try {
    const basicOpts = {};
    const basicParser = new Parser(basicOpts);
    const basicCsv = basicParser.parse(myData);
    console.log('基本转换结果:');
    console.log(basicCsv);
} catch (err) {
    console.error('基本转换出错:', err);
}

输出:

"name","age","city"
"John",30,"New York"
"Jane",25,"Los Angeles"
"Doe",35,"Chicago"

说明

  • parse() 方法将数组中的每个对象作为一行
  • 默认会提取所有对象的所有键作为字段名

三、定制转换:选择特定字段

我们可以通过 fields 选项,指定输出哪些字段,保证输出内容的可控性。

示例:只导出 nameemail

import { Parser } from '@json2csv/plainjs';

const users = [
    { name: 'Alice', age: 30, email: 'alice@example.com' },
    { name: 'Bob', age: 25, email: 'bob@example.com' }
];

const customOpts = { fields: ['name', 'email'] };
const customParser = new Parser(customOpts);
const csv = customParser.parse(users);
console.log(csv);

输出:

name,email
Alice,alice@example.com
Bob,bob@example.com

说明

  • 只提取配置中列出的字段,支持自定义顺序

四、深入:使用复杂参数定制格式

@json2csv/plainjs 提供多种选项,帮助你控制输出格式。

1. 自定义字段标题

import { Parser } from '@json2csv/plainjs';

const users = [
    { name: 'Alice', age: 30, email: 'alice@example.com' },
    { name: 'Bob', age: 25, email: 'bob@example.com' }
];

const fields = [
    { label: '姓名', value: 'name' },
    { label: '年龄', value: 'age' }
];

const customOpts = { fields };
const customParser = new Parser(customOpts);
const csv = customParser.parse(users);
console.log(csv);

输出:

姓名,年龄
Alice,30
Bob,25

2. 添加换行符和其他格式控制

除了基本功能,你还可以通过选项调整字段分隔符、换行符等。

import { Parser } from '@json2csv/plainjs';

const users = [
    { name: 'Alice', age: 30, email: 'alice@example.com' },
    { name: 'Bob', age: 25, email: 'bob@example.com' }
];

const fields = [
    { label: '姓名', value: 'name' },
    { label: '年龄', value: 'age' }
];

const options = {
    fields,
    delimiter: ';', // 使用分号作为字段分隔符
    eol: '\r\n'     // 换行符使用 Windows 风格
    
};

const customParser = new Parser(options);
const csv = customParser.parse(users);
console.log(csv);

五、实战案例:导出嵌套JSON

假设你的 JSON 中包含嵌套对象,如何处理?

import { Parser } from '@json2csv/plainjs';

const data = [
    {
        id: 1,
        name: 'Alice',
        address: { city: 'Beijing', street: 'Chao Yang' }
    },
    {
        id: 2,
        name: 'Bob',
        address: { city: 'Shanghai', street: 'Pudong' }
    }
];

// 通过指定字段的路径,将嵌套对象扁平化
const fields = [
    { label: 'ID', value: 'id' },
    { label: '名称', value: 'name' },
    { label: '城市', value: 'address.city' },
    { label: '街道', value: 'address.street' }
];

const customParser = new Parser({fields});
const csv = customParser.parse(data);
console.log(csv);

输出:

ID,名称,城市,街道
1,Alice,Beijing,Chao Yang
2,Bob,Shanghai,Pudong

结语

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。


❌