Web3.js 全面解析

Web3.js 全面解析

Web3.js 是以太坊官方推出的 JavaScript 库,用于与以太坊区块链交互。让我们深入解析其架构、核心功能和使用方法。

1. Web3.js 架构概述

核心模块结构

javascript 复制代码
// Web3.js 模块架构
const web3Architecture = {
    core: {
        web3: "主入口类",
        providers: "提供者模块 - 连接区块链",
        eth: "以太坊区块链交互",
        net: "网络信息",
        utils: "工具函数库"
    },
    extended: {
        contracts: "智能合约交互",
        accounts: "账户管理", 
        personal: "账户操作(已弃用)",
        bzz: "Swarm去中心化存储",
        shh: "Whisper协议"
    }
};

版本演进

javascript 复制代码
// Web3.js 版本对比
const web3Versions = {
    "0.x.x": {
        type: "回调模式",
        features: ["回调函数", "批量请求", "较慢"],
        status: "已弃用"
    },
    "1.x.x": {
        type: "Promise模式", 
        features: ["Promise支持", "TypeScript", "模块化"],
        status: "当前稳定版"
    },
    "4.x.x": {
        type: "现代版本",
        features: ["ESM模块", "更好TypeScript", "性能优化"],
        status: "最新版本"
    }
};

2. 安装和初始化

安装

bash 复制代码
# 使用 npm
npm install web3

# 使用 yarn  
yarn add web3

# 使用 CDN
<script src="https://cdn.jsdelivr.net/npm/web3@4.0.3/dist/web3.min.js"></script>

初始化连接

javascript 复制代码
import Web3 from 'web3';

// 多种初始化方式
class Web3Initialization {
    // 1. 使用 MetaMask 或其他注入提供者
    initWithInjectedProvider() {
        if (typeof window.ethereum !== 'undefined') {
            this.web3 = new Web3(window.ethereum);
            console.log("使用注入的以太坊提供者");
        } else {
            console.error("请安装 MetaMask!");
        }
    }
    
    // 2. 使用 HTTP 提供者
    initWithHttpProvider(rpcUrl = 'http://localhost:8545') {
        this.web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl));
        console.log("使用 HTTP 提供者连接:", rpcUrl);
    }
    
    // 3. 使用 WebSocket 提供者
    initWithWebSocketProvider(wsUrl = 'ws://localhost:8546') {
        this.web3 = new Web3(new Web3.providers.WebSocketProvider(wsUrl));
        console.log("使用 WebSocket 提供者连接:", wsUrl);
    }
    
    // 4. 使用 Infura 或 Alchemy
    initWithInfura(projectId, network = 'mainnet') {
        const infuraUrl = `https://${network}.infura.io/v3/${projectId}`;
        this.web3 = new Web3(new Web3.providers.HttpProvider(infuraUrl));
        console.log("使用 Infura 连接:", network);
    }
}

3. 核心模块详解

3.1 账户管理 (web3.eth.accounts)

javascript 复制代码
class AccountManager {
    constructor(web3) {
        this.web3 = web3;
    }
    
    // 创建新账户
    createAccount(password = '') {
        try {
            const account = this.web3.eth.accounts.create();
            console.log("新账户地址:", account.address);
            console.log("私钥:", account.privateKey);
            return account;
        } catch (error) {
            console.error("创建账户失败:", error);
        }
    }
    
    // 从私钥恢复账户
    recoverAccount(privateKey) {
        try {
            const account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
            console.log("恢复的账户地址:", account.address);
            return account;
        } catch (error) {
            console.error("恢复账户失败:", error);
        }
    }
    
    // 加密钱包
    encryptAccount(privateKey, password) {
        try {
            const keystore = this.web3.eth.accounts.encrypt(privateKey, password);
            return keystore;
        } catch (error) {
            console.error("加密账户失败:", error);
        }
    }
    
    // 解密钱包
    decryptAccount(keystoreJson, password) {
        try {
            const account = this.web3.eth.accounts.decrypt(keystoreJson, password);
            return account;
        } catch (error) {
            console.error("解密账户失败:", error);
        }
    }
    
    // 签名消息
    async signMessage(message, privateKey) {
        try {
            const signature = this.web3.eth.accounts.sign(message, privateKey);
            return signature;
        } catch (error) {
            console.error("签名失败:", error);
        }
    }
    
    // 验证签名
    verifySignature(message, signature, address) {
        try {
            const recoveredAddress = this.web3.eth.accounts.recover(message, signature);
            return recoveredAddress.toLowerCase() === address.toLowerCase();
        } catch (error) {
            console.error("验证签名失败:", error);
            return false;
        }
    }
}

3.2 区块链交互 (web3.eth)

javascript 复制代码
class BlockchainInteractor {
    constructor(web3) {
        this.web3 = web3;
    }
    
    // 获取区块信息
    async getBlockInfo(blockNumberOrHash = 'latest') {
        try {
            const block = await this.web3.eth.getBlock(blockNumberOrHash);
            return {
                number: block.number,
                hash: block.hash,
                timestamp: new Date(block.timestamp * 1000),
                transactions: block.transactions.length,
                gasUsed: block.gasUsed,
                gasLimit: block.gasLimit
            };
        } catch (error) {
            console.error("获取区块信息失败:", error);
        }
    }
    
    // 获取账户余额
    async getBalance(address, unit = 'ether') {
        try {
            const balanceWei = await this.web3.eth.getBalance(address);
            const balance = this.web3.utils.fromWei(balanceWei, unit);
            return { wei: balanceWei, formatted: balance + ' ' + unit };
        } catch (error) {
            console.error("获取余额失败:", error);
        }
    }
    
    // 获取交易信息
    async getTransaction(txHash) {
        try {
            const tx = await this.web3.eth.getTransaction(txHash);
            if (!tx) return null;
            
            return {
                hash: tx.hash,
                from: tx.from,
                to: tx.to,
                value: this.web3.utils.fromWei(tx.value, 'ether'),
                gas: tx.gas,
                gasPrice: this.web3.utils.fromWei(tx.gasPrice, 'gwei'),
                nonce: tx.nonce,
                blockNumber: tx.blockNumber
            };
        } catch (error) {
            console.error("获取交易失败:", error);
        }
    }
    
    // 获取交易收据
    async getTransactionReceipt(txHash) {
        try {
            const receipt = await this.web3.eth.getTransactionReceipt(txHash);
            return receipt;
        } catch (error) {
            console.error("获取交易收据失败:", error);
        }
    }
    
    // 估算交易 Gas
    async estimateGas(transactionObject) {
        try {
            const gas = await this.web3.eth.estimateGas(transactionObject);
            return gas;
        } catch (error) {
            console.error("估算 Gas 失败:", error);
        }
    }
    
    // 获取当前 Gas 价格
    async getGasPrice() {
        try {
            const gasPriceWei = await this.web3.eth.getGasPrice();
            const gasPriceGwei = this.web3.utils.fromWei(gasPriceWei, 'gwei');
            return { wei: gasPriceWei, gwei: gasPriceGwei };
        } catch (error) {
            console.error("获取 Gas 价格失败:", error);
        }
    }
}

3.3 交易操作

javascript 复制代码
class TransactionHandler {
    constructor(web3) {
        this.web3 = web3;
    }
    
    // 发送以太币交易
    async sendEther(fromAddress, privateKey, toAddress, amount, options = {}) {
        try {
            // 创建交易对象
            const txObject = {
                from: fromAddress,
                to: toAddress,
                value: this.web3.utils.toWei(amount.toString(), 'ether'),
                gas: options.gas || 21000,
                gasPrice: options.gasPrice || await this.web3.eth.getGasPrice(),
                nonce: options.nonce || await this.web3.eth.getTransactionCount(fromAddress, 'latest')
            };
            
            // 估算 Gas(可选)
            if (!options.gas) {
                txObject.gas = await this.web3.eth.estimateGas(txObject);
            }
            
            // 签名交易
            const signedTx = await this.web3.eth.accounts.signTransaction(txObject, privateKey);
            
            // 发送交易
            const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
            
            return {
                success: true,
                transactionHash: receipt.transactionHash,
                blockNumber: receipt.blockNumber,
                gasUsed: receipt.gasUsed
            };
        } catch (error) {
            console.error("发送交易失败:", error);
            return { success: false, error: error.message };
        }
    }
    
    // 监听待处理交易
    subscribePendingTransactions(callback) {
        try {
            const subscription = this.web3.eth.subscribe('pendingTransactions', (error, txHash) => {
                if (!error) {
                    callback(txHash);
                }
            });
            
            return subscription;
        } catch (error) {
            console.error("监听交易失败:", error);
        }
    }
    
    // 获取交易数量
    async getTransactionCount(address, blockTag = 'latest') {
        try {
            const nonce = await this.web3.eth.getTransactionCount(address, blockTag);
            return nonce;
        } catch (error) {
            console.error("获取交易数量失败:", error);
        }
    }
}

3.4 智能合约交互

javascript 复制代码
class ContractInteractor {
    constructor(web3) {
        this.web3 = web3;
        this.contracts = new Map();
    }
    
    // 初始化合约实例
    initContract(contractAddress, abi) {
        try {
            const contract = new this.web3.eth.Contract(abi, contractAddress);
            this.contracts.set(contractAddress, contract);
            return contract;
        } catch (error) {
            console.error("初始化合约失败:", error);
        }
    }
    
    // 调用只读方法(不消耗 Gas)
    async callContractMethod(contractAddress, methodName, params = [], options = {}) {
        try {
            const contract = this.contracts.get(contractAddress);
            if (!contract) throw new Error("合约未初始化");
            
            const result = await contract.methods[methodName](...params).call(options);
            return result;
        } catch (error) {
            console.error(`调用合约方法 ${methodName} 失败:`, error);
        }
    }
    
    // 发送交易到合约(消耗 Gas)
    async sendContractTransaction(contractAddress, methodName, params = [], fromAddress, privateKey, options = {}) {
        try {
            const contract = this.contracts.get(contractAddress);
            if (!contract) throw new Error("合约未初始化");
            
            // 构建交易
            const method = contract.methods[methodName](...params);
            const encodedData = method.encodeABI();
            
            const txObject = {
                from: fromAddress,
                to: contractAddress,
                data: encodedData,
                gas: options.gas || await method.estimateGas({ from: fromAddress }),
                gasPrice: options.gasPrice || await this.web3.eth.getGasPrice(),
                nonce: options.nonce || await this.web3.eth.getTransactionCount(fromAddress, 'latest')
            };
            
            // 签名并发送
            const signedTx = await this.web3.eth.accounts.signTransaction(txObject, privateKey);
            const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
            
            return {
                success: true,
                transactionHash: receipt.transactionHash,
                blockNumber: receipt.blockNumber,
                gasUsed: receipt.gasUsed,
                events: receipt.events
            };
        } catch (error) {
            console.error(`发送合约交易 ${methodName} 失败:`, error);
            return { success: false, error: error.message };
        }
    }
    
    // 监听合约事件
    subscribeToContractEvent(contractAddress, eventName, callback) {
        try {
            const contract = this.contracts.get(contractAddress);
            if (!contract) throw new Error("合约未初始化");
            
            const subscription = contract.events[eventName]({
                fromBlock: 'latest'
            }, (error, event) => {
                if (!error) {
                    callback(event);
                }
            });
            
            return subscription;
        } catch (error) {
            console.error(`监听合约事件 ${eventName} 失败:`, error);
        }
    }
    
    // 获取过去事件
    async getPastEvents(contractAddress, eventName, options = {}) {
        try {
            const contract = this.contracts.get(contractAddress);
            if (!contract) throw new Error("合约未初始化");
            
            const events = await contract.getPastEvents(eventName, {
                fromBlock: options.fromBlock || 0,
                toBlock: options.toBlock || 'latest',
                filter: options.filter || {}
            });
            
            return events;
        } catch (error) {
            console.error(`获取历史事件 ${eventName} 失败:`, error);
        }
    }
}

3.5 工具函数 (web3.utils)

javascript 复制代码
class Web3Utils {
    constructor(web3) {
        this.web3 = web3;
    }
    
    // 单位转换
    unitConversions() {
        const weiValue = '1000000000000000000'; // 1 ETH
        
        return {
            weiToEther: this.web3.utils.fromWei(weiValue, 'ether'), // "1.0"
            etherToWei: this.web3.utils.toWei('1', 'ether'), // "1000000000000000000"
            weiToGwei: this.web3.utils.fromWei(weiValue, 'gwei'), // "1000000000.0"
            gweiToWei: this.web3.utils.toWei('1', 'gwei') // "1000000000"
        };
    }
    
    // 地址工具
    addressTools() {
        const address = '0x742d35Cc6634C0532925a3b8Dc2388e46F6E2F8E';
        
        return {
            isValidAddress: this.web3.utils.isAddress(address), // true
            checkAddressChecksum: this.web3.utils.checkAddressChecksum(address),
            toChecksumAddress: this.web3.utils.toChecksumAddress(address.toLowerCase())
        };
    }
    
    // 哈希函数
    hashFunctions() {
        const message = 'Hello Web3';
        
        return {
            keccak256: this.web3.utils.keccak256(message),
            sha3: this.web3.utils.sha3(message),
            soliditySha3: this.web3.utils.soliditySha3(
                { type: 'string', value: message }
            )
        };
    }
    
    // 编码解码
    encodingDecoding() {
        const number = 255;
        const hexString = '0xff';
        
        return {
            numberToHex: this.web3.utils.numberToHex(number), // "0xff"
            hexToNumber: this.web3.utils.hexToNumber(hexString), // 255
            utf8ToHex: this.web3.utils.utf8ToHex('Hello'), // "0x48656c6c6f"
            hexToUtf8: this.web3.utils.hexToUtf8('0x48656c6c6f') // "Hello"
        };
    }
    
    // 随机数生成
    randomGeneration() {
        return {
            randomHex: this.web3.utils.randomHex(32), // 32字节随机十六进制
            randomBytes: Array.from(this.web3.utils.randomBytes(16)) // 16字节随机数组
        };
    }
}

4. 实际应用示例

完整的 DApp 集成示例

javascript 复制代码
class CompleteDApp {
    constructor(provider) {
        this.web3 = new Web3(provider);
        this.accountManager = new AccountManager(this.web3);
        this.blockchainInteractor = new BlockchainInteractor(this.web3);
        this.transactionHandler = new TransactionHandler(this.web3);
        this.contractInteractor = new ContractInteractor(this.web3);
        this.utils = new Web3Utils(this.web3);
        
        this.currentAccount = null;
    }
    
    // 连接钱包
    async connectWallet() {
        try {
            if (window.ethereum) {
                // 请求账户访问
                const accounts = await window.ethereum.request({
                    method: 'eth_requestAccounts'
                });
                
                this.currentAccount = accounts[0];
                console.log("连接的账户:", this.currentAccount);
                
                // 监听账户变化
                window.ethereum.on('accountsChanged', (accounts) => {
                    this.currentAccount = accounts[0] || null;
                    console.log("账户切换为:", this.currentAccount);
                });
                
                // 监听网络变化
                window.ethereum.on('chainChanged', (chainId) => {
                    console.log("网络切换:", parseInt(chainId));
                    window.location.reload();
                });
                
                return { success: true, account: this.currentAccount };
            } else {
                return { success: false, error: "请安装 MetaMask" };
            }
        } catch (error) {
            return { success: false, error: error.message };
        }
    }
    
    // 获取完整账户信息
    async getAccountInfo() {
        if (!this.currentAccount) {
            return { error: "请先连接钱包" };
        }
        
        try {
            const [balance, transactionCount, chainId] = await Promise.all([
                this.blockchainInteractor.getBalance(this.currentAccount),
                this.transactionHandler.getTransactionCount(this.currentAccount),
                this.web3.eth.getChainId()
            ]);
            
            return {
                address: this.currentAccount,
                balance: balance.formatted,
                transactionCount: transactionCount,
                networkId: chainId,
                isConnected: true
            };
        } catch (error) {
            return { error: error.message };
        }
    }
    
    // 完整的代币转账流程
    async tokenTransfer(tokenContractAddress, tokenABI, toAddress, amount, decimals = 18) {
        if (!this.currentAccount) {
            return { success: false, error: "请先连接钱包" };
        }
        
        try {
            // 初始化代币合约
            const tokenContract = this.contractInteractor.initContract(tokenContractAddress, tokenABI);
            
            // 获取代币余额
            const balance = await this.contractInteractor.callContractMethod(
                tokenContractAddress, 
                'balanceOf', 
                [this.currentAccount]
            );
            
            // 检查余额是否足够
            const amountInWei = BigInt(amount) * (10n ** BigInt(decimals));
            if (BigInt(balance) < amountInWei) {
                return { success: false, error: "代币余额不足" };
            }
            
            // 执行转账(这里需要私钥,实际应用中应该使用钱包签名)
            // const result = await this.contractInteractor.sendContractTransaction(
            //     tokenContractAddress,
            //     'transfer',
            //     [toAddress, amountInWei.toString()],
            //     this.currentAccount,
            //     privateKey
            // );
            
            // 在实际 DApp 中,应该使用钱包签名而不是直接使用私钥
            const transferData = tokenContract.methods.transfer(toAddress, amountInWei.toString()).encodeABI();
            
            return {
                success: true,
                message: "准备好转账",
                data: transferData,
                from: this.currentAccount,
                to: tokenContractAddress,
                value: "0",
                gasLimit: await tokenContract.methods.transfer(toAddress, amountInWei.toString()).estimateGas({ from: this.currentAccount })
            };
        } catch (error) {
            return { success: false, error: error.message };
        }
    }
}

5. 错误处理和最佳实践

javascript 复制代码
class Web3BestPractices {
    constructor(web3) {
        this.web3 = web3;
    }
    
    // 错误处理
    handleCommonErrors(error) {
        const errorMessages = {
            'User denied transaction signature': '用户取消了交易',
            'insufficient funds for transfer': '余额不足',
            'nonce too low': 'nonce 值过低',
            'gas required exceeds allowance': 'Gas 不足',
            'execution reverted': '交易执行失败',
            'invalid address': '无效的地址格式'
        };
        
        for (const [key, value] of Object.entries(errorMessages)) {
            if (error.message.includes(key)) {
                return value;
            }
        }
        
        return error.message;
    }
    
    // 安全检查
    securityChecks(address, amount) {
        const checks = {
            isValidAddress: this.web3.utils.isAddress(address),
            isZeroAddress: address === '0x0000000000000000000000000000000000000000',
            isAmountValid: amount > 0 && !isNaN(amount),
            isContractAddress: this.isContract(address) // 需要异步检查
        };
        
        return checks;
    }
    
    async isContract(address) {
        try {
            const code = await this.web3.eth.getCode(address);
            return code !== '0x';
        } catch (error) {
            return false;
        }
    }
    
    // Gas 优化策略
    async optimizeGas(transactionObject) {
        try {
            // 估算 Gas
            const estimatedGas = await this.web3.eth.estimateGas(transactionObject);
            
            // 获取当前 Gas 价格
            const currentGasPrice = await this.web3.eth.getGasPrice();
            
            // 添加缓冲(10%)
            const bufferedGas = Math.floor(estimatedGas * 1.1);
            
            return {
                gas: bufferedGas,
                gasPrice: currentGasPrice,
                maxFeePerGas: currentGasPrice,
                maxPriorityFeePerGas: this.web3.utils.toWei('1', 'gwei')
            };
        } catch (error) {
            console.error("Gas 优化失败:", error);
            return null;
        }
    }
}

6. 与 ethers.js 对比

javascript 复制代码
const web3VsEthers = {
    web3js: {
        advantages: [
            "官方以太坊库",
            "功能全面",
            "文档完善",
            "社区庞大"
        ],
        disadvantages: [
            "包体积较大",
            "API 较复杂",
            "Tree-shaking 支持一般"
        ],
        useCases: [
            "企业级应用",
            "需要全面功能",
            "传统 Web 开发背景"
        ]
    },
    ethersjs: {
        advantages: [
            "轻量级",
            "API 简洁",
            "TypeScript 支持好",
            "Tree-shaking 友好"
        ],
        disadvantages: [
            "功能相对较少",
            "某些高级功能缺失"
        ],
        useCases: [
            "前端重点的应用",
            "需要更好性能",
            "现代前端开发"
        ]
    }
};

总结

Web3.js 是以太坊生态中最成熟的 JavaScript 库,提供了:

  1. 完整的区块链交互 - 账户、交易、合约、事件
  2. 丰富的工具函数 - 单位转换、哈希计算、编码解码
  3. 多种连接方式 - HTTP、WebSocket、注入提供者
  4. 强大的合约支持 - 完整的 ABI 处理、事件监听
  5. 企业级可靠性 - 经过大量项目验证

虽然新兴的 ethers.js 在某些场景下更有优势,但 Web3.js 凭借其功能全面性和稳定性,仍然是许多企业和大型项目的首选。

相关推荐
前端开发爱好者2 小时前
前端新玩具:Vike 发布!
前端·javascript
今天也是爱大大的一天吖2 小时前
vue2中的.native修饰符和$listeners组件属性
前端·javascript·vue.js
fxshy2 小时前
在 Vue 3 + Vite 项目中使用 Less 实现自适应布局:VW 和 VH 的应用
前端·javascript·less
奇舞精选2 小时前
AI时代的前端知识拾遗:前端事件循环机制详解(基于 WHATWG 最新规范)
前端·javascript
小月鸭2 小时前
理解预处理器(Sass/Less)
前端
AI3D_WebEngineer3 小时前
企业级业务平台项目设计、架构、业务全解之组件库篇
前端·javascript·vue
charlie1145141913 小时前
从零开始理解 CSS:让网页“活”起来的语言2
前端·css·笔记·学习·选择器·样式表·原生
浪裡遊3 小时前
Next.js路由系统
开发语言·前端·javascript·react.js·node.js·js
mapbar_front3 小时前
职场中的顶级能力—服务意识
前端