如何实现浏览器的Console功能
离 JS-Encoder 的最初版本发布已经过了大半年的时间,这段时间除了偶尔修复一下 BUG 外,主要还是忙于学业。最近一段时间不太平,开学时间也大大延迟,加上自己本身对自己的在线编译器不是很满意,于是我花了一个多月的时间从头到尾重新写了一个。
如果你对旧版JS-Encoder有兴趣的话,可以看看:
既然是重新写,那当然是连界面也要重新设计,说实话我早就看旧版界面不爽了。作为一个小前端,根本不懂什么UI,完全是凭感觉。
这是旧版的JS-Encoder:

这是新版的JS-Encoder:

以前把5个窗口挤在一起给人的体验确实不好,所以我把三个代码窗口叠在一起,每次只显示一个,Console我放到下面来显示...好吧其实我就是照着VSCoder做的╮(╯-╰)╭。
至于功能上和旧版其实差不多,只是修复了一些既有BUG、增加了用户系统还有我要讲的Console。
我虽然在旧版实现了Console,但是实在是过于简陋,相比Chrome自带的Console,我自己写的都不能叫Console了,就是一个日志查看器而已。
在新版里,我使用Console实现了一些常用方法:
- log
- warn
- error
- info
- time
- timeLog
- timeEnd
- clear
由于浏览器没有提供直接获取日志信息的途径,除了直接重写iframe元素的Console,我也想不出更好的办法。深思熟虑之后我写了一个Console类用来处理日志,其运作流程如下:

首先我来说说单纯显示日志的功能如何实现:
当用户编辑JavaScript输出日志的时候,会调用我重写过的Console对象的方法,这些方法参数列表和方法名(比如用户写了console.log(1,2,3),那么方法名就是'log',参数列表就是[1,2,3])传递给转换器,转换器遍历参数列表,根据参数的数据类型以及方法名赋予其不同的样式,流程如下:

转换器会对参数列表中的参数逐个判断,如果为基本类型(number, string, boolean, symbol, null, undefined)的话,直接将该参数拼接在html字符串中,根据调用方法的不同赋予不同的样式(比如error方法就将字体颜色设为红色,warn方法设为橙色,log方法根据数据类型不同设置不同的颜色等等)。
假如用户这样写:
console.log(1, '2', false);
那么经过转换器转换出来的结果就应该是下面的html字符串数组:
// 不同的样式代表不同的颜色
[
'<span class="cm-number">1</span>',
'<span class="cm-string">"2"</span>',
'<span class="cm-boolean">false</span>'
]
然后再渲染到页面上效果如下:

如果是混合类型(也就是引用类型,因为这些引用类型通常包含若干不同数据类型的值,所以我喜欢叫混合类型),我选择直接通过CodeMirror插件来达到代码高亮的效果,因为引用类型比较复杂,自己来处理费时费力,还是用现成的,简单的基本类型就没必要用CodeMirror处理了,太浪费性能。
将引用类型转换成字符串不能够直接使用JSON.stringify,我们知道这样转化出来的结果会是这样:

那么这样的缺点有如下:
- 里面有很多双引号
- JSON.stringify无法处理循环引用
- Symbol类型无法直接转换成字符串
- Function类型无法直接转换成字符串
- DOM类型无法直接转换成字符串
......
既然有那么多限制,那么就要有选择地使用JSON.stringify。对于对象或者是数组,需要遍历其中属性,判断各个属性类型并通过不同的方式将其转换为字符串,方法如下:
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进行高亮处理:

同样地,除了高亮之外,对于warn、error和info也采用与log同样的方式。

另外,因为有些对象过于庞大(如Window),放在页面上显示太消耗性能,对于这样的对象会弹出警告,提示去浏览器自带的控制台查看。
对于time、timeLog、timeEnd这三个方法,需要使用到内置的performance对象
用一个Map对象来存储计时器名称和时间映射。
如果用户编辑:
console.time('timer1');
这个时候Map对象里就会添加一个‘timer1’的映射:
setTimer (name) {
const timer = this.timer;
timer.set(name, performance.now());
}
当然Date.now()同样也可以计时,但精确度就不如performance.now()了。
当timeLog或者timeEnd方法被触发,只要返回时间差就好了:
calcTime (name) {
const time = this.timerName.get(name);
return `${name}: ${performance.now() - time} ms`;
}
然后再做一下高亮处理,效果如下:

输出方面大致就是这些,如果以后有添加新功能,我会第一时间更新文章。
Console除了输出,还需要输入功能,再来看看这张图:

用户可以通过命令输入框来输入命令,将命令传给执行者进行处理。
当用户输入命令并按下回车,Console窗口需要显示的是命令本身,以及命令的返回值。
执行者需要做下面的事情:
- 在iframe作用域内执行命令。
- 获取命令执行后的返回值。
- 调用console对象的方法输出命令本身和返回值。
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,代码略
// ...
}
效果如下:

当然,命令输入框还可以缓存历史命令,使用上下方向键可以调出历史命令
这个功能需要将所有历史命令存入一个数组中,流程如下:

实现起来还蛮简单,效果如下:

对于iframe本身产生的错误,需要改写其window对象的onerror事件:
this.window.onerror = (msg, _, row, col) => {
consoleInfo.push({ // 通过console对象的error方法输出
type: 'system-error',
content: msg,
row,
col
})
return false
}
输出的错误包含所在行和列

最后,可以通过过滤功能控制日志类型显示:

虽然经历了一路坎坷,但是这让我学习了很多东西,也获得了前所未有的自豪感,我会继续更新JS-Encoder,项目的GitHub仓库地址和项目地址如下,如果你喜欢它,可以点个star支持我!
如何实现浏览器的Console功能的更多相关文章
- 浏览器控制台console的使用
前天在团队项目中因为产品需求在提交订单的时候需要多个页面的数据作为提交接口的参数,这种需求让人醉醉的,项目用angular来做的,没办法只能用全局变量来定义要交互的值和localStorage来临时的 ...
- 浏览器中 F12 功能的简单介绍
chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己也在不断摸索中,查看一些博客和资料后,自己总结了一下来帮助自己理解和记忆,也希望能帮到有需要的小伙伴,嘿嘿! 首 ...
- [转]chrome浏览器中 F12 功能的简单介绍
本文转自:https://www.cnblogs.com/zhuzhubaoya/p/9758648.html chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己 ...
- 【F12】chrome浏览器中 F12 功能的简单介绍
chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己也在不断摸索中,查看一些博客和资料后,自己总结了一下来帮助自己理解和记忆,也希望能帮到有需要的小伙伴,嘿嘿! 首 ...
- chrome浏览器中 F12 功能的简单介绍
chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己也在不断摸索中,查看一些博客和资料后,自己总结了一下来帮助自己理解和记忆,也希望能帮到有需要的小伙伴,嘿嘿! 首 ...
- 尝试在你浏览器的Console(F12)中运行一下,你会发现页面被不同的颜色块高亮了(css调试中学到的js)
现在到处都是JavaScript,每天都能知道点新东西.一旦你入了门,你总能从这里或是那里领悟到很多知识. 一旦我发现一些有意思的东西,我喜欢去感觉他们的源代码,看一看它是怎么办到的. 今天我想分享A ...
- js禁止浏览器页面后退功能
js禁止浏览器页面后退功能: <script> $(function(){ ) { //防止页面后退 history.pushState(null, null, document.URL) ...
- 利用原生JS实现类似浏览器查找高亮功能(转载)
利用原生JS实现类似浏览器查找高亮功能 在完成 Navify 时,增加一个类似浏览器ctrl+f查找并该高亮的功能,在此进行一点总结: 需求 在.content中有许多.box,需要在.box中找出搜 ...
- 利用浏览器的console篡改cookie
背景: 最近公司有个客户问题,是由于浏览器的cookie中多记录过期的session id导致重复登录,普通操作无法复现,因此尝试进行cookie篡改复现问题. 方法: 首先,要知道软件定义的sess ...
随机推荐
- LeetCode--二叉树1--树的遍历
LeetCode--二叉树1--树的遍历 一 深度遍历 深度遍历里面由 三种遍历方式,两种实现方法.都要熟练掌握. 值得注意的是,当你删除树中的节点时,删除过程将按照后序遍历的顺序进行. 也就是说,当 ...
- JavaScript实现队列结构(Queue)
JavaScript实现队列结构(Queue) 一.队列简介 队列是是一种受限的线性表,特点为先进先出(FIFO:first in first out). 受限之处在于它只允许在表的前端(front) ...
- 一些大厂的css reset 代码
不同的浏览器对标签的默认值不同,为了避免页面出现浏览器差异,所以要初始化样式表属性.使用通配符*并不可取,因为会遍历到每一个标签,大型网页会加载过慢,影响性能. 雅虎工程师提供的CSS初始化示例代码: ...
- [转帖]RSYNC 的核心算法
RSYNC 的核心算法 https://coolshell.cn/articles/7425.html rsync是unix/linux下同步文件的一个高效算法,它能同步更新两处计算机的文件与目录,并 ...
- mongodb忘记密码处理步骤
mongodb忘记密码的处理办法较MySQL等数据库而言方法显得更加暴力,处理方式如下: 1. 修改mongodb的配置文件 mongodb的配置文件一般可以通过查看进程的方式查看文件名,例如: p ...
- 【转】JS内置对象方法
String内置对象的方法 1. concat() concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串 var str = "Hello"; var out ...
- 个人项目作业(wc.exe)
1.GitHub项目地址 https://github.com/QiuBin666/WC 项目介绍: 题目描述 Word Count1. 实现一个简单而完整的软件工具(源程序特征统计程序).2. 进行 ...
- 基于kylinTOP工具的HTTP2压力测试
1.HTTP协议概述 说到http,那就应该先了解一下http协议的发展历史.关于http协议的历史,可以参考阮一峰老师的这篇博客文章HTTP 协议入门,里面介绍的比较详细了.简单来说http先后存在 ...
- 何为内存模型(JMM)?
前言 任何一门语言都有其语言规范,从逻辑上我们可划分为语法规范和语义规范,语法规范则是描述了如何通过相关语法编写可执行的程序,而语义规范则是指通过语法编写的程序所构造出的具体含义.语言只要具备存储(比 ...
- Java 基础(六):循环
循环 老生常谈的一个控制流程了,我们在是使用数组和集合的时候,遍历元素的时候经常会用到循环的结构,Java具有非常灵活的三种循环机制: 根据是否知道循环的次数可以为分为while循环,do...whi ...