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),放在页面上显示太消耗性能,对于这样的对象会弹出警告,提示去浏览器自带的控制台查看。

对于timetimeLogtimeEnd这三个方法,需要使用到内置的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支持我!

GitHub JS-Encoder

JS-Encoder

如何实现浏览器的Console功能的更多相关文章

  1. 浏览器控制台console的使用

    前天在团队项目中因为产品需求在提交订单的时候需要多个页面的数据作为提交接口的参数,这种需求让人醉醉的,项目用angular来做的,没办法只能用全局变量来定义要交互的值和localStorage来临时的 ...

  2. 浏览器中 F12 功能的简单介绍

    chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己也在不断摸索中,查看一些博客和资料后,自己总结了一下来帮助自己理解和记忆,也希望能帮到有需要的小伙伴,嘿嘿! 首 ...

  3. [转]chrome浏览器中 F12 功能的简单介绍

    本文转自:https://www.cnblogs.com/zhuzhubaoya/p/9758648.html chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己 ...

  4. 【F12】chrome浏览器中 F12 功能的简单介绍

    chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己也在不断摸索中,查看一些博客和资料后,自己总结了一下来帮助自己理解和记忆,也希望能帮到有需要的小伙伴,嘿嘿! 首 ...

  5. chrome浏览器中 F12 功能的简单介绍

    chrome浏览器中 F12 功能的简单介绍 由于F12是前端开发人员的利器,所以我自己也在不断摸索中,查看一些博客和资料后,自己总结了一下来帮助自己理解和记忆,也希望能帮到有需要的小伙伴,嘿嘿! 首 ...

  6. 尝试在你浏览器的Console(F12)中运行一下,你会发现页面被不同的颜色块高亮了(css调试中学到的js)

    现在到处都是JavaScript,每天都能知道点新东西.一旦你入了门,你总能从这里或是那里领悟到很多知识. 一旦我发现一些有意思的东西,我喜欢去感觉他们的源代码,看一看它是怎么办到的. 今天我想分享A ...

  7. js禁止浏览器页面后退功能

    js禁止浏览器页面后退功能: <script> $(function(){ ) { //防止页面后退 history.pushState(null, null, document.URL) ...

  8. 利用原生JS实现类似浏览器查找高亮功能(转载)

    利用原生JS实现类似浏览器查找高亮功能 在完成 Navify 时,增加一个类似浏览器ctrl+f查找并该高亮的功能,在此进行一点总结: 需求 在.content中有许多.box,需要在.box中找出搜 ...

  9. 利用浏览器的console篡改cookie

    背景: 最近公司有个客户问题,是由于浏览器的cookie中多记录过期的session id导致重复登录,普通操作无法复现,因此尝试进行cookie篡改复现问题. 方法: 首先,要知道软件定义的sess ...

随机推荐

  1. JavaScript实现栈结构(Stack)

    JavaScript实现栈结构(Stack) 一.前言 1.1.什么是数据结构? 数据结构就是在计算机中,存储和组织数据的方式. 例如:图书管理,怎样摆放图书才能既能放很多书,也方便取? 主要需要考虑 ...

  2. @开发者,快来申请你的工业级NXP内核物联网开发板

    米尔工业级NXP开发板试用活动(MYD-C8MMX) 不久前 米尔推出了新一代高性价比核心板之王 MYC-C8MMX核心板及开发板 获得众多客户热烈反馈 这一次 我们给各位带来福利 i.MX8M mi ...

  3. 【Amaple教程】5. 插件

    Amaple 拥有非常强大插件功能,这也是它的突出功能之一,Amaple插件一般表现为功能块(函数)或功能块(包含一系列函数和属性的Object对象),它除了支持Amaple规范定义的插件外,还支持所 ...

  4. 最新版jdk 13环境变量配置

    1.配置环境变量 右击“我的电脑”-->"属性"-->"高级系统设置"-->"高级"-->"环境变量&qu ...

  5. 使用Navicat Premiun远程连接MySQL失败,报错(10038)

    远程连接MySQL失败,可能有一下原因: 1.小伙子/小姑凉注意一下你的ip是否输入正确了!! 2.网络或防火墙问题 1).排查网络问题 使用命令:ping 192.168.1.1 查看网络请求是否超 ...

  6. 06 yarn是什么

    yarn集群中有两个角色: 主节点:Resource Manager  1台 从节点:Node Manager   N台 Resource Manager一般安装在一台专门的机器上 Node Mana ...

  7. openwrt 外挂usb 网卡 RTL8188CU 及添加 RT5572 kernel支持

    RT5572 原来叫 Ralink雷凌 现在被 MTK 收购了,淘宝上买的很便宜50块邮,2.4 5G 双频.在 win10 上插了试试,果然是支持 5G.这上面写着 飞荣 是什么牌子,有知道的和我说 ...

  8. nes 红白机模拟器 第5篇 全屏显示

    先看一下效果图 放大的原理是使用最初级的算法,直接取对应像素法. /*================================================================= ...

  9. java第一次上机练习作业

    1.已知a,b均是整型变量,写出将a,b两个变量中的值互换的程序.(知识点:变量和 运算符综合应用) int a = 5, b = 10; int temp; temp = a; a = b; b = ...

  10. Asp.Net Core AuthorizeAttribute 和AuthorizeFilter 跟进及源码解读

    一.前言 IdentityServer4已经分享了一些应用实战的文章,从架构到授权中心的落地应用,也伴随着对IdentityServer4掌握了一些使用规则,但是很多原理性东西还是一知半解,故我这里持 ...