模拟实现console.dir函数

本文介绍了一个使用JavaScript实现的对象属性遍历与显示方法,通过递归方式展示对象的属性和方法,并支持折叠展开功能。此外,还介绍了如何对属性进行排序以提高可读性。

function dir(obj,name,initContainer){
var ul = initContainer ? initContainer : document.createElement("ul");
var li = document.createElement("li");
var span = document.createElement("span");
span.innerHTML = "+"
span.className = "plus";
span.onclick = function(){
if(this.rendered){
if(this.className === "minus"){
this.className = "plus";
this.innerHTML = "+"
this.parentNode.lastChild.style.display = "block";
}else{
this.className = "minus";
this.innerHTML = "-"
this.parentNode.lastChild.style.display = "none";
}
return ;
}
var ul = document.createElement("ul");
for(var k in obj){
dir(obj[k],k,ul)
}
li.appendChild(ul);

this.className = "minus";
this.innerHTML = "-"
this.rendered = true;
}
li.appendChild(span);
var span2 = document.createElement("span");
name = name || obj.toString();
span2.innerHTML = name + " : " + typeof obj;
li.appendChild(span2);
ul.appendChild(li);
return ul
}

/*-----------------------------Test dir--------------------------------*/
window.onload = function(){
document.body.appendChild(dir(window));
}

测试打印出window对象的所有属性和方法,见附件。

另外还可以排序,以下按属性和方法的名称排序(当然也可以按照typeof类型排序):

var properties = [];
for(var p in obj){
properties.push(p);
}
properties.sort();
var len = properties.length;
for(var i=0;i<len;i++){
dir(obj[properties[i]],properties[i],ul)
}
li.appendChild(ul);
const express = require('express'); const multer = require('multer'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const app = express(); const port = 3000; // 中间件 app.use(cors()); app.use(express.json()); // 健康检查端点 app.get('/api/health', (req, res) => { const modelStatus = checkModelStatus(); res.json({ status: 'ok', message: '语音识别服务运行正常', modelStatus: modelStatus }); }); // 配置multer用于文件上传 const storage = multer.diskStorage({ destination: (req, file, cb) => { const uploadDir = 'uploads'; if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir); } cb(null, uploadDir); }, filename: (req, file, cb) => { const ext = path.extname(file.originalname) || '.mp3'; cb(null, Date.now() + ext); } }); const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024, }, fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('audio/')) { cb(null, true); } else { cb(new Error('只支持音频文件'), false); } } }); // Vosk模型路径 const modelPath = './vosk-model'; // 详细的模型检查函数 const checkModelStatus = () => { if (!fs.existsSync(modelPath)) { return { exists: false, message: '模型文件夹不存在', requiredFiles: ['am/', 'conf/', 'graph/'], suggestion: '请下载并重命名模型文件夹为 vosk-model' }; } const requiredDirs = ['am', 'conf', 'graph']; const missingDirs = requiredDirs.filter(dir => !fs.existsSync(path.join(modelPath, dir)) ); if (missingDirs.length > 0) { return { exists: true, complete: false, missing: missingDirs, message: '模型文件夹不完整', suggestion: '请下载完整的Vosk中文模型' }; } // 检查关键文件 const requiredFiles = [ 'conf/mfcc.conf', 'conf/model.conf', 'graph/HCLr.fst', 'graph/Gr.fst' ]; const missingFiles = requiredFiles.filter(file => !fs.existsSync(path.join(modelPath, file)) ); if (missingFiles.length > 0) { return { exists: true, complete: false, missingFiles: missingFiles, message: '模型文件不完整' }; } return { exists: true, complete: true, message: '模型完整可用' }; }; // 检查Vosk模块是否可用 let voskAvailable = false; try { require('vosk'); voskAvailable = true; console.log('✅ Vosk模块加载成功'); } catch (e) { console.warn('❌ Vosk模块不可用:', e.message); } // 模拟识别函数 const simulateRecognition = (filePath) => { return new Promise((resolve) => { setTimeout(() => { const phrases = [ "这是一个语音识别测试结果", "欢迎使用语音转文字服务", "今天的天气真不错", "人工智能正在改变世界", "语音识别技术越来越先进了" ]; const randomText = phrases[Math.floor(Math.random() * phrases.length)]; resolve({ success: true, text: randomText, simulated: true }); }, 1500); }); }; // 语音转文字API app.post('/api/speech-to-text', upload.single('audio'), async (req, res) => { console.log('收到音频文件:', req.file?.filename); if (!req.file) { return res.status(400).json({ error: '没有上传音频文件' }); } try { const modelStatus = checkModelStatus(); // 如果模型不存在或不完整,使用模拟模式 if (!modelStatus.exists || !modelStatus.complete) { console.log('使用模拟模式,模型状态:', modelStatus); const result = await simulateRecognition(req.file.path); // 清理文件 if (fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); } return res.json({ ...result, modelStatus: modelStatus }); } // 如果Vosk模块不可用,也使用模拟模式 if (!voskAvailable) { console.log('Vosk模块不可用,使用模拟模式'); const result = await simulateRecognition(req.file.path); if (fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); } return res.json({ ...result, modelStatus: modelStatus }); } // 使用真实的Vosk识别 console.log('使用Vosk进行语音识别'); const vosk = require('vosk'); const model = new vosk.Model(modelPath); console.log("🚀 ~ modelPath:", modelPath) const rec = new vosk.Recognizer({ model: model, sampleRate: 16000 }); console.log("🚀 ~ rec:", rec) const audioData = fs.readFileSync(req.file.path); console.log("🚀 ~ audioData:", audioData) let result; if (rec.acceptWaveform(audioData)) { result = rec.result(); } else { result = rec.partialResult(); } console.log("🚀 ~ result:", result) // 清理资源 rec.free(); if (fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); } res.json({ success: true, text: result.text || '未识别到内容', result: result, modelStatus: modelStatus }); } catch (error) { console.error('语音识别错误:', error); // 清理文件 if (req.file && fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); } res.status(500).json({ error: '语音识别处理失败', details: error.message, modelStatus: checkModelStatus() }); } }); // 获取模型信息的API app.get('/api/model-info', (req, res) => { const modelStatus = checkModelStatus(); res.json(modelStatus); }); // 启动服务 app.listen(port, '0.0.0.0', () => { console.log('='.repeat(60)); console.log('🚀 语音识别服务启动成功'); console.log('='.repeat(60)); console.log(`📍 本地访问: http://localhost:${port}`); console.log('='.repeat(60)); // 检查模型状态 const modelStatus = checkModelStatus(); console.log('📦 模型状态:'); console.log(' - 存在:', modelStatus.exists); console.log(' - 完整:', modelStatus.complete); if (!modelStatus.exists) { console.log('❌ 模型文件夹不存在'); console.log('💡 请执行以下步骤:'); console.log(' 1. 访问 https://alphacephei.com/vosk/models'); console.log(' 2. 下载 vosk-model-cn-0.22.zip'); console.log(' 3. 解压并重命名文件夹为 vosk-model'); console.log(' 4. 放置在项目根目录'); } else if (!modelStatus.complete) { console.log('❌ 模型不完整'); console.log('💡 缺失内容:', modelStatus.missing || modelStatus.missingFiles); console.log('💡 请下载完整的Vosk中文模型'); } else { console.log('✅ 模型完整可用'); } console.log('='.repeat(60)); console.log('📋 测试接口:'); console.log(` - 健康检查: http://localhost:${port}/api/health`); console.log(` - 模型信息: http://localhost:${port}/api/model-info`); console.log('='.repeat(60)); });后端代码如上,前端代码如下:recorderManager.start({ duration: 60000, // 录音时长,单位ms sampleRate: 44100, // 采样率 numberOfChannels: 1, // 录音通道数 encodeBitRate: 192000, // 编码码率 format: 'mp3' // 音频格式,支持aac/mp3 }),发送录音文件后,后端代码输出:收到音频文件: 1755842922597.mp3 使用Vosk进行语音识别 LOG (VoskAPI:ReadDataFiles():model.cc:213) Decoding params beam=12 max-active=5000 lattice-beam=4 LOG (VoskAPI:ReadDataFiles():model.cc:216) Silence phones 1:2:3:4:5:6:7:8:9:10 LOG (VoskAPI:RemoveOrphanNodes():nnet-nnet.cc:948) Removed 0 orphan nodes. LOG (VoskAPI:RemoveOrphanComponents():nnet-nnet.cc:847) Removing 0 orphan components. LOG (VoskAPI:ReadDataFiles():model.cc:248) Loading i-vector extractor from ./vosk-model/ivector/final.ie LOG (VoskAPI:ComputeDerivedVars():ivector-extractor.cc:183) Computing derived variables for iVector extractor LOG (VoskAPI:ComputeDerivedVars():ivector-extractor.cc:204) Done. LOG (VoskAPI:ReadDataFiles():model.cc:282) Loading HCL and G from ./vosk-model/graph/HCLr.fst ./vosk-model/graph/Gr.fst LOG (VoskAPI:ReadDataFiles():model.cc:303) Loading winfo ./vosk-model/graph/phones/word_boundary.int 🚀 ~ modelPath: ./vosk-model 🚀 ~ rec: Recognizer { handle: <Buffer@0x00000292DA298F40 type: { size: 0, indirection: 1, get: [Function: get], set: [Function: set], name: 'void', ffi_type: <Buffer@0x00007FFE45082CF8 name: 'void'> }> } 🚀 ~ audioData: <Buffer@0x00000292C6E68F40 ff fb 90 c4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 54701 more bytes> 🚀 ~ result: { partial: '' }为什么识别不到文字
08-23
分析以下JS代码,在websocket信令正确传递信息,建立数据通道后验证数据通道已建立,但为何数据通道接收后无法在客户端显示let channel = null // [全局变量 存储数据通道] let pc = null // [队列数据结构实现] class Queue { enqueue(value) { const node = {value} // [创建新节点] if (this.tail) this.tail.next = node; else this.head = node; this.tail = node; // [更新尾部为新节点] } dequeue() { if (!this.head) return null; const value = this.head.value; this.head = this.head.next; if (!this.head) this.tail = null; return value; // [返回出队值] } } const incoming = new Queue() // [创建全局队列实例] class Media { // [初始化WebRTC连接] initConnection(stream) { if (this.peer) { return } let configuration = { 'iceServers':[{ 'url':'stun:192.168.0.66:3478' },] } //console.log("初始化WebRTC连接 ...") this.peer = new RTCPeerConnection(configuration) //console.log("ICE候选信息处理 ...") this.peer.onicecandidate = e => { if (!e.candidate) { return } var pTemp = JSON.stringify({ event: 'candidate', data: JSON.stringify(e.candidate.toJSON()) }) this.ws.send(pTemp) console.log("WS发送 本地candidate ..." + pTemp) } // [添加媒体轨道] stream.getTracks().forEach(t => { this.peer.addTrack(t, stream) }) // [数据通道处理] this.peer.ondatachannel = (e) => { e.channel.onmessage = (ee) => { // [处理接收到的消息 放入队列] if (ee.data.arrayBuffer) { ee.data.arrayBuffer().then(data => incoming.enqueue(data)) } else { incoming.enqueue(ee.data) } //console.log("数据 ..." + ee.data) } e.channel.onopen = () => { channel = e.channel // [存储全局通道引用] this.resolve() // [解析连接Promise] } console.log("数据通道 OK ...") } } // [建立连接] connect(stream) { return new Promise(resolve => { this.resolve = resolve; this.ws = new WebSocket("ws://192.168.0.66:27016") //this.ws = new WebSocket("ws://127.0.0.1:27016/websocket") this.ws.onopen = () => { //this.ws.send('WS OK ...') } const handler = async (e) => { //console.log("初始化WebRTC连接 ...") this.initConnection(stream) //console.log("解析WebSocket消息 ...") const parsed = JSON.parse(e.data) if (parsed.event === 'offer') { console.log("WS接收 远程SDP ...\n", parsed) await this.peer.setRemoteDescription(JSON.parse(parsed.data)) const answer = await this.peer.createAnswer() await this.peer.setLocalDescription(answer) var pTemp = JSON.stringify({ event: 'answer', data: JSON.stringify(answer) }) this.ws.send(pTemp) console.log("WS发送 本地SDP ...\n" + pTemp) } if (parsed.event === 'candidate') { console.log("WS接收 处理candidate ...", parsed) await this.peer.addIceCandidate(JSON.parse(parsed.data)) } } console.log("WS接收 添加消息处理器 ...") this.ws.addEventListener('message', handler) }) } } const media = new Media() // [创建媒体实例] async function connect() { const stream = await navigator.mediaDevices.getUserMedia({audio: true}) // [获取音频流] await media.connect(stream) // [建立连接] } // [Emscripten模块初始化完成回调] Module.onRuntimeInitialized = () => { //console.log("包装C函数供JS调用 ...") window.Cmd_ExecuteString = Module.cwrap('Cmd_ExecuteString', null, ['string']); window.Sys_Quit = Module.cwrap('Sys_Quit', null, []); //console.log("检测触摸支持 ...") setTimeout(() => { const supportsHover = window.matchMedia('(hover: hover)').matches; if (!supportsHover) { window.Cmd_ExecuteString('touch_enable 1') } }, 5000) // [发送回调函数] const sendtoCallback = (message, length, flags) => { // [从堆中获取数据] const view = HEAPU8.subarray(message, message + length); if (channel) { // [通过数据通道发送] channel.send(view) console.log("通过数据通道发送 ..." + view) } } const sendtoFuncPtr = addFunction(sendtoCallback, 'viii'); // 创建函数指针 Module.ccall('retgister_sendto_callback', null, ['number'], [sendtoFuncPtr]); // 注册回调 // [接收回调函数] const recvfromCallback = (sockfd, buf, len, flags, src_addr, addrlen) => { // [从队列中获取数据] const data = incoming.dequeue() if (!data) { return -1 } const dataView = new Uint8Array(data); const copyLen = Math.min(len, dataView.length); HEAPU8.set(dataView.subarray(0, copyLen), buf); // [设置源地址信息] if (src_addr) { HEAP16[src_addr >> 1] = 2; HEAP8[(src_addr + 2)] = 0x1F; HEAP8[(src_addr + 3)] = 0x90; HEAP8[(src_addr + 4)] = 127; HEAP8[(src_addr + 5)] = 0; HEAP8[(src_addr + 6)] = 0; HEAP8[(src_addr + 7)] = 1; } if (addrlen) { HEAP32[addrlen >> 2] = 16; } console.log("从队列中获取数据 ..." + data) return copyLen; } const recvfromFuncPtr = addFunction(recvfromCallback, 'iiiiiii'); Module.ccall('retgister_recvfrom_callback', null, ['number'], [recvfromFuncPtr]); } // [文件系统初始化] async function fsInit() { // [获取并解压ZIP文件] const res = await fetch('/public/valve.zip') const zip = await JSZip.loadAsync(await res.arrayBuffer()); // [遍历ZIP文件内容] for (const [filename, file] of Object.entries(zip.files)) { // [跳过目录] if (file.dir) continue; const path = '/rodir/' + filename; const dir = path.split('/').slice(0, -1).join('/'); // [创建目录结构] await FS.mkdirTree(dir); // [写入文件] await FS.writeFile(path, await file.async("uint8array")); } // [切换工作目录] await FS.chdir('/rodir') } async function start() { await Promise.all([fsInit(), connect()]); preInit(); run(); } start()
最新发布
10-05
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值