网站Logo 小李的博客

Python中调用JavaScript的完整指南

xiaoli
7
2025-05-23

Python中调用JavaScript的完整指南

引言

在现代Web开发和自动化领域,Python和JavaScript经常需要协同工作。Python以其简洁的语法和强大的数据处理能力著称,而JavaScript则在前端交互和异步处理方面表现出色。有时候,我们需要在Python环境中执行JavaScript代码,比如:

  • 爬虫需要执行页面中的JavaScript来获取动态内容
  • 数据处理时需要使用某些JavaScript库的功能
  • 自动化测试中需要模拟浏览器行为
  • 需要复用已有的JavaScript业务逻辑

本文将详细介绍在Python中调用JavaScript的几种主要方法,帮助初学者选择最适合自己项目的解决方案。

方法一:PyExecJS - 最简单直接的方式

简介

PyExecJS是一个Python库,允许你直接在Python中执行JavaScript代码。它支持多种JavaScript引擎,包括Node.js、PhantomJS等。

安装

pip install PyExecJS

如果需要Node.js支持(推荐):

# 安装Node.js(官网下载或使用包管理器)
npm install -g node

基础用法

import execjs

# 方式1:直接执行JavaScript代码
result = execjs.eval("1 + 2")
print(result)  # 输出: 3

# 方式2:编译JavaScript代码后执行
js_code = """
function greet(name) {
    return "Hello, " + name + "!";
}

function calculate(a, b) {
    return {
        sum: a + b,
        product: a * b,
        difference: a - b
    };
}
"""

# 编译JavaScript代码
ctx = execjs.compile(js_code)

# 调用JavaScript函数
greeting = ctx.call("greet", "Python Developer")
print(greeting)  # 输出: Hello, Python Developer!

math_result = ctx.call("calculate", 10, 5)
print(math_result)  # 输出: {'sum': 15, 'product': 50, 'difference': 5}

处理复杂数据

import execjs
import json

# 复杂的JavaScript代码示例
js_code = """
function processData(data) {
    // 对数组进行处理
    var processed = data.map(function(item) {
        return {
            id: item.id,
            name: item.name.toUpperCase(),
            score: item.score * 1.1,  // 加权处理
            grade: item.score >= 90 ? 'A' : 
                   item.score >= 80 ? 'B' : 
                   item.score >= 70 ? 'C' : 'D'
        };
    });
    
    // 计算统计信息
    var totalScore = processed.reduce(function(sum, item) {
        return sum + item.score;
    }, 0);
    
    return {
        data: processed,
        stats: {
            count: processed.length,
            averageScore: totalScore / processed.length
        }
    };
}
"""

ctx = execjs.compile(js_code)

# Python数据
students = [
    {"id": 1, "name": "alice", "score": 85},
    {"id": 2, "name": "bob", "score": 92},
    {"id": 3, "name": "charlie", "score": 78}
]

# 调用JavaScript函数处理数据
result = ctx.call("processData", students)
print(json.dumps(result, indent=2))

优缺点分析

优点:

  • 安装和使用简单,学习成本低
  • 支持多种JavaScript引擎
  • 可以直接传递Python对象到JavaScript
  • 适合执行简单到中等复杂度的JavaScript代码

缺点:

  • 性能不如原生JavaScript引擎
  • 不支持DOM操作和浏览器特定API
  • 对于大型JavaScript应用支持有限
  • 调试相对困难

方法二:Node.js子进程 - 高性能方案

简介

通过Python的subprocess模块调用Node.js来执行JavaScript代码,这种方法性能较好,适合处理复杂的JavaScript逻辑。

基础用法

import subprocess
import json
import tempfile
import os

def execute_js_with_node(js_code, input_data=None):
    """
    使用Node.js执行JavaScript代码
    """
    # 创建临时JavaScript文件
    with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
        # 如果有输入数据,将其作为全局变量
        if input_data:
            f.write(f"var inputData = {json.dumps(input_data)};\n")
        
        f.write(js_code)
        
        # 添加输出处理
        f.write("""
        
// 如果结果是一个变量,输出它
if (typeof result !== 'undefined') {
    console.log(JSON.stringify(result));
}
        """)
        
        js_file = f.name
    
    try:
        # 执行JavaScript文件
        result = subprocess.run(
            ['node', js_file],
            capture_output=True,
            text=True,
            check=True
        )
        
        # 解析输出
        if result.stdout.strip():
            return json.loads(result.stdout.strip())
        return None
        
    except subprocess.CalledProcessError as e:
        print(f"JavaScript执行错误: {e.stderr}")
        return None
    except json.JSONDecodeError as e:
        print(f"JSON解析错误: {e}")
        print(f"原始输出: {result.stdout}")
        return None
    finally:
        # 清理临时文件
        os.unlink(js_file)

# 使用示例
js_code = """
// 复杂的数据处理逻辑
function analyzeData(data) {
    const analysis = {
        totalItems: data.length,
        categories: {},
        priceStats: {
            min: Infinity,
            max: -Infinity,
            total: 0
        }
    };
    
    data.forEach(item => {
        // 统计分类
        if (!analysis.categories[item.category]) {
            analysis.categories[item.category] = 0;
        }
        analysis.categories[item.category]++;
        
        // 价格统计
        const price = parseFloat(item.price);
        analysis.priceStats.min = Math.min(analysis.priceStats.min, price);
        analysis.priceStats.max = Math.max(analysis.priceStats.max, price);
        analysis.priceStats.total += price;
    });
    
    analysis.priceStats.average = analysis.priceStats.total / analysis.totalItems;
    
    return analysis;
}

// 使用输入数据
var result = analyzeData(inputData);
"""

# 准备测试数据
test_data = [
    {"name": "商品1", "category": "电子", "price": "299.99"},
    {"name": "商品2", "category": "服装", "price": "89.50"},
    {"name": "商品3", "category": "电子", "price": "199.00"},
    {"name": "商品4", "category": "图书", "price": "29.99"},
    {"name": "商品5", "category": "服装", "price": "129.99"}
]

# 执行JavaScript代码
result = execute_js_with_node(js_code, test_data)
if result:
    print("数据分析结果:")
    print(json.dumps(result, indent=2, ensure_ascii=False))

异步处理示例

import subprocess
import json
import tempfile
import os

def execute_async_js(js_code):
    """
    执行包含异步操作的JavaScript代码
    """
    # 包装异步代码
    wrapped_code = f"""
const util = require('util');

async function main() {{
    try {{
        {js_code}
    }} catch (error) {{
        console.error(JSON.stringify({{ error: error.message }}));
    }}
}}

main();
    """
    
    with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
        f.write(wrapped_code)
        js_file = f.name
    
    try:
        result = subprocess.run(
            ['node', js_file],
            capture_output=True,
            text=True,
            timeout=30  # 30秒超时
        )
        
        if result.stdout.strip():
            return json.loads(result.stdout.strip())
        return None
    except Exception as e:
        print(f"执行错误: {e}")
        return None
    finally:
        os.unlink(js_file)

# 异步JavaScript示例
async_js_code = """
// 模拟异步操作
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function fetchData() {
    console.log(JSON.stringify({status: "开始获取数据..."}));
    await delay(1000);
    
    const data = {
        users: [
            {id: 1, name: "张三", age: 25},
            {id: 2, name: "李四", age: 30},
            {id: 3, name: "王五", age: 28}
        ],
        timestamp: new Date().toISOString()
    };
    
    console.log(JSON.stringify({status: "数据获取完成", data: data}));
}

await fetchData();
"""

result = execute_async_js(async_js_code)
if result:
    print("异步执行结果:")
    print(json.dumps(result, indent=2, ensure_ascii=False))

优缺点分析

优点:

  • 性能优秀,使用原生Node.js引擎
  • 支持所有Node.js特性和npm包
  • 可以处理复杂的异步操作
  • 资源消耗相对较小

缺点:

  • 需要安装Node.js环境
  • 进程间通信有一定开销
  • 调试相对复杂
  • 不支持浏览器特定的API

方法三:js2py - 纯Python实现

简介

js2py是一个纯Python实现的JavaScript解释器,可以将JavaScript代码转换为Python代码执行。

安装和使用

pip install js2py
import js2py

# 基础使用
result = js2py.eval_js("3 + 4")
print(result)  # 输出: 7

# 执行JavaScript函数
js_code = """
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

function processArray(arr) {
    return arr.map(function(x) {
        return x * 2;
    }).filter(function(x) {
        return x > 10;
    });
}
"""

# 编译JavaScript代码
context = js2py.EvalJs()
context.execute(js_code)

# 调用函数
fib_result = context.fibonacci(10)
print(f"斐波那契数列第10项: {fib_result}")

array_result = context.processArray([1, 2, 3, 4, 5, 6, 7, 8])
print(f"数组处理结果: {list(array_result)}")

优缺点分析

优点:

  • 纯Python实现,无需额外依赖
  • 安装简单
  • 与Python集成良好

缺点:

  • 性能较差
  • JavaScript兼容性有限
  • 不支持现代JavaScript特性
  • 社区支持相对较少

方法对比总结

方法性能易用性功能完整性资源消耗适用场景
PyExecJS⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐简单JavaScript逻辑执行
Node.js子进程⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐复杂JavaScript应用
js2py⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐简单脚本,无依赖需求

实际应用案例

案例1:数据加密解密

import execjs

# JavaScript加密算法
crypto_js = """
function simpleEncrypt(text, key) {
    var result = '';
    for (var i = 0; i < text.length; i++) {
        var charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
        result += String.fromCharCode(charCode);
    }
    return btoa(result);  // Base64编码
}

function simpleDecrypt(encryptedText, key) {
    var decoded = atob(encryptedText);  // Base64解码
    var result = '';
    for (var i = 0; i < decoded.length; i++) {
        var charCode = decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length);
        result += String.fromCharCode(charCode);
    }
    return result;
}
"""

ctx = execjs.compile(crypto_js)

# 加密数据
original_text = "这是需要加密的敏感数据"
secret_key = "mySecretKey123"

encrypted = ctx.call("simpleEncrypt", original_text, secret_key)
print(f"加密后: {encrypted}")

decrypted = ctx.call("simpleDecrypt", encrypted, secret_key)
print(f"解密后: {decrypted}")

案例2:文本处理和分析

import execjs

# JavaScript文本分析算法
text_analysis_js = """
function analyzeText(text) {
    // 基础统计
    var stats = {
        totalChars: text.length,
        totalWords: 0,
        totalSentences: 0,
        wordFrequency: {},
        averageWordLength: 0
    };
    
    // 分词(简单版本,以空格分割)
    var words = text.toLowerCase()
                   .replace(/[^a-zA-Z0-9\s\u4e00-\u9fa5]/g, '')
                   .split(/\s+/)
                   .filter(function(word) { return word.length > 0; });
    
    stats.totalWords = words.length;
    
    // 词频统计
    var totalWordLength = 0;
    words.forEach(function(word) {
        stats.wordFrequency[word] = (stats.wordFrequency[word] || 0) + 1;
        totalWordLength += word.length;
    });
    
    stats.averageWordLength = totalWordLength / words.length;
    
    // 句子统计(简单版本)
    stats.totalSentences = text.split(/[.!?]+/).filter(function(s) { 
        return s.trim().length > 0; 
    }).length;
    
    // 找出最常用的词
    var sortedWords = Object.keys(stats.wordFrequency).sort(function(a, b) {
        return stats.wordFrequency[b] - stats.wordFrequency[a];
    });
    
    stats.topWords = sortedWords.slice(0, 5).map(function(word) {
        return {
            word: word,
            count: stats.wordFrequency[word]
        };
    });
    
    return stats;
}

function summarizeText(text, maxSentences) {
    maxSentences = maxSentences || 3;
    
    // 简单的文本摘要算法
    var sentences = text.split(/[.!?]+/).filter(function(s) { 
        return s.trim().length > 10; 
    });
    
    if (sentences.length <= maxSentences) {
        return sentences.join('. ') + '.';
    }
    
    // 计算每个句子的权重(基于词频)
    var words = text.toLowerCase().split(/\W+/);
    var wordFreq = {};
    words.forEach(function(word) {
        if (word.length > 2) {
            wordFreq[word] = (wordFreq[word] || 0) + 1;
        }
    });
    
    var sentenceScores = sentences.map(function(sentence) {
        var sentenceWords = sentence.toLowerCase().split(/\W+/);
        var score = 0;
        sentenceWords.forEach(function(word) {
            score += wordFreq[word] || 0;
        });
        return {
            sentence: sentence.trim(),
            score: score / sentenceWords.length
        };
    });
    
    // 选择得分最高的句子
    sentenceScores.sort(function(a, b) { return b.score - a.score; });
    
    return sentenceScores.slice(0, maxSentences)
                        .map(function(item) { return item.sentence; })
                        .join('. ') + '.';
}
"""

ctx = execjs.compile(text_analysis_js)

# 示例文本
sample_text = """
人工智能是计算机科学的一个分支,它试图理解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器。
人工智能的研究包括机器学习、深度学习、自然语言处理等多个领域。
机器学习是人工智能的核心技术之一,通过算法使计算机能够从数据中学习并做出预测或决策。
深度学习作为机器学习的一个分支,使用神经网络来模拟人脑的学习过程。
自然语言处理则致力于让计算机理解和生成人类语言。
这些技术的发展正在改变我们的生活方式,从智能手机的语音助手到自动驾驶汽车,人工智能无处不在。
"""

# 分析文本
analysis_result = ctx.call("analyzeText", sample_text)
print("文本分析结果:")
print(f"总字符数: {analysis_result['totalChars']}")
print(f"总词数: {analysis_result['totalWords']}")
print(f"总句数: {analysis_result['totalSentences']}")
print(f"平均词长: {analysis_result['averageWordLength']:.2f}")
print("高频词汇:")
for word_info in analysis_result['topWords']:
    print(f"  {word_info['word']}: {word_info['count']}次")

# 生成摘要
summary = ctx.call("summarizeText", sample_text, 2)
print(f"\n文本摘要:\n{summary}")

常见问题和解决方案

PyExecJS常见问题

1. 找不到JavaScript引擎

错误信息:

execjs._exceptions.RuntimeUnavailable: Could not find a JavaScript runtime

解决方案:

import execjs

# 检查可用的JavaScript引擎
print(execjs.get().name)

# 如果没有可用引擎,安装Node.js
# Windows: 从官网下载安装包
# macOS: brew install node
# Ubuntu: sudo apt-get install nodejs npm

# 验证安装
import subprocess
try:
    result = subprocess.run(['node', '--version'], capture_output=True, text=True)
    print(f"Node.js版本: {result.stdout.strip()}")
except FileNotFoundError:
    print("Node.js未正确安装")

2. 中文字符编码问题

**问题:**处理中文时出现乱码或编码错误

解决方案:

import execjs
import json

# 错误的做法 - 直接传递包含特殊字符的文本
js_code = """
function processUserInfo(text) {
    return "处理结果: " + text;
}
"""

# 问题场景:包含换行符、引号等特殊字符的中文文本
problematic_text = "用户名:张三\n密码:123456\t备注:\"重要用户\""

try:
    ctx_wrong = execjs.compile(js_code)
    # 直接传递可能导致JavaScript语法错误
    result_wrong = ctx_wrong.call("processUserInfo", problematic_text)
    print("可能有问题的输出:", result_wrong)
except Exception as e:
    print(f"编码错误: {e}")

# 正确的做法 - 使用JSON序列化确保安全传递
def safe_text_processing():
    """使用JSON编码确保文本安全传递"""
    # 关键:使用json.dumps处理特殊字符
    safe_text = json.dumps(problematic_text, ensure_ascii=False)
    
    # 修改JavaScript代码,先解析JSON字符串
    js_with_parse = """
    function processUserInfo(jsonText) {
        var text = JSON.parse(jsonText);
        return "处理结果: " + text;
    }
    """
    
    ctx = execjs.compile(js_with_parse)
    result = ctx.call("processUserInfo", safe_text)
    return result

# 处理中文文本时确保正确编码
result = safe_text_processing()
print("正确输出:", result)

# 更通用的解决方案
def recommended_approach(text, js_function_code, function_name, *args):
    """推荐的中文文本处理方法"""
    try:
        # 1. 确保文本是字符串类型
        if not isinstance(text, str):
            text = str(text)
        
        # 2. 使用JSON序列化处理特殊字符
        safe_text = json.dumps(text, ensure_ascii=False)
        
        # 3. 创建包装的JavaScript代码
        wrapped_js = f"""
        {js_function_code}
        
        function wrapper(jsonStr) {{
            var actualText = JSON.parse(jsonStr);
            return {function_name}(actualText);
        }}
        """
        
        # 4. 执行
        ctx = execjs.compile(wrapped_js)
        return ctx.call("wrapper", safe_text)
        
    except Exception as e:
        return f"处理失败: {str(e)}"

# 使用推荐方法
final_result = recommended_approach(
    "测试文本\n包含换行\t和制表符\"引号",
    js_code,
    "processUserInfo"
)
print(f"最终结果: {final_result}")

3. 复杂对象传递问题

**问题:**传递复杂Python对象时失败

解决方案:

import execjs
import json
from datetime import datetime

# 错误的做法 - 直接传递复杂对象
class CustomObject:
    def __init__(self, name, value):
        self.name = name
        self.value = value

# 正确的做法 - 转换为简单数据类型
def safe_object_conversion(obj):
    """安全地转换对象为JavaScript可识别的格式"""
    if isinstance(obj, datetime):
        return obj.isoformat()
    elif hasattr(obj, '__dict__'):
        return {k: safe_object_conversion(v) for k, v in obj.__dict__.items()}
    elif isinstance(obj, (list, tuple)):
        return [safe_object_conversion(item) for item in obj]
    else:
        return obj

js_code = """
function processObject(obj) {
    return {
        originalName: obj.name,
        upperName: obj.name.toUpperCase(),
        doubleValue: obj.value * 2
    };
}
"""

ctx = execjs.compile(js_code)
custom_obj = CustomObject("测试", 42)
safe_obj = safe_object_conversion(custom_obj)
result = ctx.call("processObject", safe_obj)
print(result)

Node.js子进程常见问题

1. Node.js未安装或路径问题

错误信息:

FileNotFoundError: [Errno 2] No such file or directory: 'node'

解决方案:

import subprocess
import shutil
import os

def check_node_installation():
    """检查Node.js安装状态"""
    # 检查node命令是否可用
    node_path = shutil.which('node')
    if node_path:
        print(f"Node.js安装路径: {node_path}")
        
        # 检查版本
        try:
            result = subprocess.run(['node', '--version'], 
                                  capture_output=True, text=True, check=True)
            print(f"Node.js版本: {result.stdout.strip()}")
            return True
        except subprocess.CalledProcessError as e:
            print(f"Node.js执行错误: {e}")
            return False
    else:
        print("Node.js未安装或不在PATH中")
        print("解决方案:")
        print("1. 从 https://nodejs.org 下载安装Node.js")
        print("2. 确保Node.js添加到系统PATH中")
        print("3. 重启终端或IDE")
        return False

# 使用检查函数
if not check_node_installation():
    print("请先安装Node.js再继续")

2. 临时文件权限问题

**问题:**在某些系统上创建临时文件失败

解决方案:

import tempfile
import os
import subprocess
import json

def safe_js_execution_with_file_handling(js_code, input_data=None):
    """安全的JavaScript执行,带完善的文件处理"""
    js_file = None
    try:
        # 创建临时文件
        with tempfile.NamedTemporaryFile(mode='w', suffix='.js', 
                                       delete=False, encoding='utf-8') as f:
            if input_data:
                f.write(f"var inputData = {json.dumps(input_data, ensure_ascii=False)};\n")
            
            f.write(js_code)
            f.write("""
if (typeof result !== 'undefined') {
    console.log(JSON.stringify(result));
}
            """)
            js_file = f.name
        
        # 执行JavaScript
        result = subprocess.run(
            ['node', js_file],
            capture_output=True,
            text=True,
            check=True,
            timeout=30,
            encoding='utf-8'
        )
        
        if result.stdout.strip():
            return json.loads(result.stdout.strip())
        return None
        
    except subprocess.TimeoutExpired:
        return {"error": "JavaScript执行超时"}
    except subprocess.CalledProcessError as e:
        return {"error": f"JavaScript执行失败: {e.stderr}"}
    except json.JSONDecodeError as e:
        return {"error": f"JSON解析失败: {e}"}
    except Exception as e:
        return {"error": f"未知错误: {str(e)}"}
    finally:
        # 清理临时文件
        if js_file and os.path.exists(js_file):
            try:
                os.unlink(js_file)
            except OSError:
                pass  # 忽略文件删除错误

3. 内存泄漏问题

**问题:**长时间运行时内存占用持续增长

解决方案:

import subprocess
import psutil
import os

class NodeJSExecutor:
    """Node.js执行器,带内存监控"""
    
    def __init__(self, memory_limit_mb=100):
        self.memory_limit = memory_limit_mb * 1024 * 1024  # 转换为字节
        self.execution_count = 0
    
    def execute_js(self, js_code, input_data=None):
        """执行JavaScript代码并监控内存使用"""
        self.execution_count += 1
        
        # 每执行50次检查一次内存
        if self.execution_count % 50 == 0:
            self._check_memory_usage()
        
        return safe_js_execution_with_file_handling(js_code, input_data)
    
    def _check_memory_usage(self):
        """检查当前进程内存使用"""
        process = psutil.Process(os.getpid())
        memory_info = process.memory_info()
        
        if memory_info.rss > self.memory_limit:
            print(f"警告: 内存使用超过限制 ({memory_info.rss / 1024 / 1024:.2f} MB)")
            print("建议重启程序或优化代码")

# 使用示例
executor = NodeJSExecutor(memory_limit_mb=200)

js2py常见问题

1. JavaScript兼容性问题

**问题:**现代JavaScript语法不被支持

解决方案:

import js2py

# 错误的做法 - 使用现代JavaScript语法
modern_js = """
const data = [1, 2, 3, 4, 5];
const result = data.map(x => x * 2).filter(x => x > 5);
"""

# 正确的做法 - 使用ES5语法
compatible_js = """
var data = [1, 2, 3, 4, 5];
var doubled = [];
for (var i = 0; i < data.length; i++) {
    doubled.push(data[i] * 2);
}
var result = [];
for (var j = 0; j < doubled.length; j++) {
    if (doubled[j] > 5) {
        result.push(doubled[j]);
    }
}
"""

try:
    context = js2py.EvalJs()
    context.execute(compatible_js)
    print(list(context.result))  # 输出: [6, 8, 10]
except Exception as e:
    print(f"执行错误: {e}")

2. 性能问题

**问题:**js2py执行速度慢

解决方案:

import js2py
import time

def benchmark_js_execution():
    """比较不同方法的性能"""
    
    # 测试数据
    test_data = list(range(1000))
    
    # js2py方法
    start_time = time.time()
    js_code = """
    function processArray(arr) {
        var result = [];
        for (var i = 0; i < arr.length; i++) {
            result.push(arr[i] * 2 + 1);
        }
        return result;
    }
    """
    
    context = js2py.EvalJs()
    context.execute(js_code)
    js_result = context.processArray(test_data)
    js2py_time = time.time() - start_time
    
    # 纯Python方法(更快的替代方案)
    start_time = time.time()
    python_result = [x * 2 + 1 for x in test_data]
    python_time = time.time() - start_time
    
    print(f"js2py执行时间: {js2py_time:.4f}秒")
    print(f"Python执行时间: {python_time:.4f}秒")
    print(f"性能差异: {js2py_time / python_time:.2f}倍")
    
    # 建议
    if js2py_time > python_time * 5:
        print("建议:考虑使用纯Python实现或其他JavaScript执行方案")

# benchmark_js_execution()

常见踩坑点和最佳实践

1. 数据类型转换陷阱

import execjs

# 陷阱1: JavaScript的类型自动转换
js_code = """
function compareValues(a, b) {
    return {
        equal: a == b,           // 可能出现意外的类型转换
        strictEqual: a === b,    // 严格比较
        type_a: typeof a,
        type_b: typeof b
    };
}
"""

ctx = execjs.compile(js_code)

# 测试不同的数据类型组合
test_cases = [
    (1, "1"),
    (0, False),
    (None, "null"),
    ([], "")
]

for a, b in test_cases:
    result = ctx.call("compareValues", a, b)
    print(f"比较 {a} 和 {b}:")
    print(f"  宽松比较: {result['equal']}")
    print(f"  严格比较: {result['strictEqual']}")
    print(f"  类型: {result['type_a']} vs {result['type_b']}")
    print()

2. 异步代码处理陷阱

# 陷阱2: 试图在PyExecJS中使用异步代码
import execjs

# 错误的做法
wrong_async_js = """
async function fetchData() {
    return new Promise(resolve => {
        setTimeout(() => resolve("数据"), 1000);
    });
}

// 这在PyExecJS中不会正常工作
var result = fetchData();
"""

# 正确的做法 - 使用同步代码或Node.js子进程
correct_sync_js = """
function processData() {
    // 使用同步逻辑替代异步操作
    var data = "模拟获取的数据";
    return {
        data: data,
        timestamp: new Date().toISOString()
    };
}

var result = processData();
"""

try:
    ctx = execjs.compile(correct_sync_js)
    result = ctx.eval("result")
    print("同步执行结果:", result)
except Exception as e:
    print(f"执行错误: {e}")

3. 内存管理陷阱

import execjs
import gc

# 陷阱3: 不正确的上下文管理
class JSContextManager:
    """JavaScript上下文管理器"""
    
    def __init__(self):
        self.contexts = {}
        self.max_contexts = 10
    
    def get_context(self, js_code, context_id=None):
        """获取或创建JavaScript执行上下文"""
        if context_id is None:
            context_id = hash(js_code)
        
        if context_id not in self.contexts:
            # 限制上下文数量,防止内存泄漏
            if len(self.contexts) >= self.max_contexts:
                # 清理最旧的上下文
                oldest_key = next(iter(self.contexts))
                del self.contexts[oldest_key]
                gc.collect()
            
            self.contexts[context_id] = execjs.compile(js_code)
        
        return self.contexts[context_id]
    
    def clear_contexts(self):
        """清理所有上下文"""
        self.contexts.clear()
        gc.collect()

# 使用示例
js_manager = JSContextManager()

# 重复使用相同的上下文
js_code = """
function calculate(x, y) {
    return x * y + Math.random();
}
"""

for i in range(100):
    ctx = js_manager.get_context(js_code, "calculator")
    result = ctx.call("calculate", i, 2)
    if i % 20 == 0:
        print(f"第{i}次计算结果: {result}")

# 清理资源
js_manager.clear_contexts()

4. 错误处理最佳实践

import execjs
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def robust_js_execution(js_code, function_name, *args, **kwargs):
    """健壮的JavaScript执行函数"""
    
    max_retries = kwargs.get('max_retries', 3)
    timeout = kwargs.get('timeout', 30)
    
    for attempt in range(max_retries):
        try:
            # 验证JavaScript代码
            if not js_code.strip():
                raise ValueError("JavaScript代码不能为空")
            
            # 编译和执行
            ctx = execjs.compile(js_code)
            
            # 检查函数是否存在
            try:
                result = ctx.call(function_name, *args)
                logger.info(f"JavaScript执行成功,尝试次数: {attempt + 1}")
                return {"success": True, "data": result, "attempts": attempt + 1}
                
            except execjs.ProgramError as e:
                if "is not defined" in str(e):
                    return {"success": False, "error": f"函数 '{function_name}' 未定义"}
                raise
            
        except execjs.RuntimeError as e:
            logger.warning(f"JavaScript运行时错误 (尝试 {attempt + 1}/{max_retries}): {e}")
            if attempt == max_retries - 1:
                return {"success": False, "error": f"运行时错误: {str(e)}"}
        
        except Exception as e:
            logger.error(f"意外错误 (尝试 {attempt + 1}/{max_retries}): {e}")
            if attempt == max_retries - 1:
                return {"success": False, "error": f"意外错误: {str(e)}"}
    
    return {"success": False, "error": "超过最大重试次数"}

# 使用示例
js_code_with_error = """
function riskyFunction(x) {
    if (x < 0) {
        throw new Error("输入值不能为负数");
    }
    return Math.sqrt(x);
}
"""

# 测试错误处理
test_values = [4, -1, 16, "invalid"]
for value in test_values:
    result = robust_js_execution(js_code_with_error, "riskyFunction", value)
    print(f"输入 {value}: {result}")

最佳实践建议

1. 选择合适的方法

  • 简单JavaScript逻辑:使用PyExecJS
  • 复杂业务逻辑:使用Node.js子进程
  • 无依赖需求:使用js2py

2. 性能优化

# 对于PyExecJS,复用编译后的上下文
ctx = execjs.compile(js_code)  # 只编译一次
for data in dataset:
    result = ctx.call("processData", data)  # 多次调用

3. 错误处理

import execjs

def safe_js_execution(js_code, function_name, *args):
    """
    安全执行JavaScript代码
    """
    try:
        ctx = execjs.compile(js_code)
        result = ctx.call(function_name, *args)
        return {"success": True, "data": result}
    except execjs.RuntimeError as e:
        return {"success": False, "error": f"JavaScript运行时错误: {str(e)}"}
    except Exception as e:
        return {"success": False, "error": f"未知错误: {str(e)}"}

# 使用示例
result = safe_js_execution(js_code, "myFunction", arg1, arg2)
if result["success"]:
    print("执行成功:", result["data"])
else:
    print("执行失败:", result["error"])

结论

Python调用JavaScript有三种主要方法,每种方法都有其适用的场景:

  • PyExecJS适合初学者和简单应用,易于上手
  • Node.js子进程适合高性能要求和复杂JavaScript逻辑
  • js2py适合对依赖有严格要求的环境

选择合适的方法需要考虑项目需求、性能要求、维护成本等因素。建议初学者从PyExecJS开始,随着需求复杂度的增加,再考虑其他方案。

无论选择哪种方法,都要注意错误处理、性能优化和安全性问题,确保代码的健壮性和可靠性。


希望这篇文章能帮助你更好地理解和使用Python调用JavaScript的各种方法。如果你有任何问题或建议,欢迎交流讨论!

动物装饰