亲宝软件园·资讯

展开

如何实现浏览器的Console功能

lliiooiill 人气:0
离 **JS-Encoder** 的最初版本发布已经过了大半年的时间,这段时间除了偶尔修复一下 BUG 外,主要还是忙于学业。最近一段时间不太平,开学时间也大大延迟,加上自己本身对自己的在线编译器不是很满意,于是我花了一个多月的时间从头到尾重新写了一个。 如果你对旧版JS-Encoder有兴趣的话,可以看看: [如何制作一款在线编译器](https://www.cnblogs.com/FrankLongger/p/11223796.html) 既然是重新写,那当然是连界面也要重新设计,说实话我早就看旧版界面不爽了。作为一个小前端,根本不懂什么UI,完全是凭感觉。 这是旧版的JS-Encoder: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403193518372-229471666.png) 这是新版的JS-Encoder: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403193554943-56378217.png) 以前把5个窗口挤在一起给人的体验确实不好,所以我把三个代码窗口叠在一起,每次只显示一个,Console我放到下面来显示...好吧其实我就是照着VSCoder做的╮(╯-╰)╭。 至于功能上和旧版其实差不多,只是修复了一些既有BUG、增加了用户系统还有我要讲的Console。 我虽然在旧版实现了Console,但是实在是过于简陋,相比Chrome自带的Console,我自己写的都不能叫Console了,就是一个日志查看器而已。 在新版里,我使用Console实现了一些常用方法: - log - warn - error - info - time - timeLog - timeEnd - clear 由于浏览器没有提供直接获取日志信息的途径,除了直接重写iframe元素的Console,我也想不出更好的办法。深思熟虑之后我写了一个Console类用来处理日志,其**运作流程**如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403193705127-1790657806.png) 首先我来说说单纯**显示日志**的功能如何实现: 当用户编辑JavaScript输出日志的时候,会调用我重写过的Console对象的方法,这些方法参数列表和方法名(比如用户写了`console.log(1,2,3)`,那么方法名就是`'log'`,参数列表就是`[1,2,3]`)传递给转换器,转换器遍历参数列表,根据参数的数据类型以及方法名赋予其不同的样式,流程如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403193816591-208215637.png) 转换器会对参数列表中的参数逐个判断,如果为基本类型(`number`, `string`, `boolean`, `symbol`, `null`, `undefined`)的话,直接将该参数拼接在html字符串中,根据调用方法的不同赋予不同的样式(比如error方法就将字体颜色设为红色,warn方法设为橙色,log方法根据数据类型不同设置不同的颜色等等)。 假如用户这样写: ```javascript console.log(1, '2', false); ``` 那么经过转换器转换出来的结果就应该是下面的html字符串数组: ```javascript // 不同的样式代表不同的颜色 [ '1', '"2"', 'false' ] ``` 然后再渲染到页面上效果如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403193940517-754611811.png) 如果是混合类型(也就是引用类型,因为这些引用类型通常包含若干不同数据类型的值,所以我喜欢叫混合类型),我选择直接通过CodeMirror插件来达到代码高亮的效果,因为引用类型比较复杂,自己来处理费时费力,还是用现成的,简单的基本类型就没必要用CodeMirror处理了,太浪费性能。 将引用类型转换成字符串不能够直接使用`JSON.stringify`,我们知道这样转化出来的结果会是这样: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403193958347-677736963.png) 那么这样的缺点有如下: - 里面有很多双引号 - JSON.stringify无法处理循环引用 - Symbol类型无法直接转换成字符串 - Function类型无法直接转换成字符串 - DOM类型无法直接转换成字符串 ...... 既然有那么多限制,那么就要有选择地使用`JSON.stringify`。对于对象或者是数组,需要遍历其中属性,判断各个属性类型并通过不同的方式将其转换为字符串,方法如下: ```javascript function JSONStringify (stringObject) { const type = judge.judgeType(stringObject) // 判断stringObject的类型 if (type !== 'Object' && type !== 'Array' && type !== 'Map') { return JSON.stringify(stringObject) } let prefix = '{', suffix = '}' // 用于包裹最后转换出的内容 if (type === 'Array') { prefix = '[' suffix = ']' } let str = prefix const keys = getObjAllKeys(stringObject) // 获取对象的所有键,包括不可枚举的键 for (let i = 0;i < keys.length;i++) { // 遍历 const key = keys[i] let value = stringObject[key] if(type === 'Map') value = stringObject.get(key) try { if (type !== 'Array') { // 判断键的类型,普通对象的键只能是字符串类型,但Map却可以突破这个限制,需要进一步判断类型 const keyType = judge.judgeType(key) switch (keyType) { case 'Object': case 'Array': case 'symbol': str += Object.prototype.toString.call(key) break default: str += key } str += ': ' } let valueType = judge.judgeType(value) if (/^HTML/.test(valueType)) valueType = 'dom' // 如果类型以HTML开头,说明是DOM元素 switch (valueType) { case 'Array': str += JSONStringify(value) break case 'Object': str += JSONStringify(value) break case 'function': str += String(value) break case 'symbol': str += String(value) break case 'dom': str += stringifyDOM(value) // 将DOM转化为字符串 break default: str += JSON.stringify(value) } if (i < keys.length - 1) str += ', ' } catch (e) { continue } } str += suffix return str } ``` 经过上面的方法可以将之前的对象a转化为: `{a: 1,b: "2",c: false,d: Symbol(123)}` 但是这样得到的是一个排成一行的字符串,很不好看,所以需要进行格式化,我使用**pretty**插件进行格式化: (pretty)[https://www.npmjs.com/package/pretty] 然后再传给CodeMirror进行高亮处理: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194211509-1370875057.png) 同样地,除了高亮之外,对于warn、error和info也采用与log同样的方式。 ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194220245-1063583416.png) 另外,因为有些对象过于庞大(如Window),放在页面上显示太消耗性能,对于这样的对象会弹出警告,提示去浏览器自带的控制台查看。 对于`time`、`timeLog`、`timeEnd`这三个方法,需要使用到内置的`performance`对象 **用一个Map对象来存储计时器名称和时间映射**。 如果用户编辑: ```javascript console.time('timer1'); ``` 这个时候Map对象里就会添加一个`‘timer1’`的映射: ```javascript setTimer (name) { const timer = this.timer; timer.set(name, performance.now()); } ``` 当然`Date.now()`同样也可以计时,但精确度就不如`performance.now()`了。 当`timeLog`或者`timeEnd`方法被触发,只要返回时间差就好了: ```javascript calcTime (name) { const time = this.timerName.get(name); return `${name}: ${performance.now() - time} ms`; } ``` 然后再做一下高亮处理,效果如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194400008-1395092227.png) 输出方面大致就是这些,如果以后有添加新功能,我会第一时间更新文章。 Console除了输出,还需要输入功能,再来看看这张图: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194409747-1336051656.png) 用户可以通过命令输入框来输入命令,将命令传给执行者进行处理。 当用户输入命令并按下回车,**Console窗口需要显示的是命令本身,以及命令的返回值**。 执行者需要做下面的事情: - 在iframe作用域内执行命令。 - 获取命令执行后的返回值。 - 调用console对象的方法输出命令本身和返回值。 ```javascript executeCommand (cmd) { let returnVal this.console.log(cmd) // 输出命令本身 try { // 由于用户输入的命令可能会出错,因此需要try catch一下,将错误取出渲染到console上 returnVal = this.window.eval(cmd) // 在iframe的window对象内执行命令 } catch (e) { this.consoleInfo.push({ // 将错误push到日志列表 type: 'error', content: e }) return } // 如果没有错误,调用重写的console.log,代码略 // ... } ``` 效果如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403195106355-1429426502.gif) 当然,命令输入框还可以缓存历史命令,使用上下方向键可以调出历史命令 这个功能需要将所有历史命令存入一个数组中,流程如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194500808-1240994823.png) 实现起来还蛮简单,效果如下: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403195031655-1894580445.gif) 对于iframe本身产生的错误,需要改写其window对象的`onerror`事件: ```javascript this.window.onerror = (msg, _, row, col) => { consoleInfo.push({ // 通过console对象的error方法输出 type: 'system-error', content: msg, row, col }) return false } ``` 输出的错误包含所在行和列 ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194615597-600483233.png) 最后,可以通过过滤功能控制日志类型显示: ![](https://img2020.cnblogs.com/blog/1368022/202004/1368022-20200403194956326-1832187958.gif) 虽然经历了一路坎坷,但是这让我学习了很多东西,也获得了前所未有的自豪感,我会继续更新JS-Encoder,**项目的GitHub仓库地址和项目地址**如下,如果你喜欢它,可以点个star支持我! [GitHub JS-Encoder](https://github.com/Longgererer/JS-Encoder) [JS-Encoder](https://www.lliiooiill.cn/JSEncoderEnhance)

加载全部内容

相关教程
猜你喜欢
用户评论