身为一个程序员, 你肯定每天都在打console.log, 假设一天打印100个console.log, 每个耗时2秒. 那我跟你探讨个方法, 可以让时间降至0.1秒. 那1天可以节省多少时间. 而且自己打印的console, 各种信息不全. 再加上现在vue3语法中ref对数据的包装, 单纯console.log已经看不到数据了, 必须加toRaw, 或者手动打开才能看到数据. 这其中浪费的时间和让人抓狂的感觉, 你有过吗? 反正强迫症的我, 不能忍.
1. 基础功能
话不多说, 步入正题. 先思考个问题, 如果是你, 你如何打印这段代码?
js
const currentVar = ref('当前变量')
如果是一个正常的操作会有以下几步
- 选中currentVal
- 复制
- cmd+回车到下一行
- 输入 console.log();
- 拷贝
以上打印所暴漏的问题
- 步骤繁琐
- 要输入的代码过多, 耗时且费力还容易写错
- 无法直接在控制台上看到数据, 需要点开多个层级才能看到里面的proxy
- 代码位置不清晰, 不知道当前代码在哪个文件
- 代码行数不清楚, 不知道当前代码在哪一行
- 无法区分, 具体是哪个变量值. 比如, 如果是两个console, 我怎么知道哪个是var1, 哪个是var2?
- 打印的代码太普通, 无法做到醒目. 试问如果是一个超大文件, 里边有1000行的console, 你怎么快速找出你打印的那一行console?
- 如果是obj对象数据, 不能直接直观的看到的看到里边的数据, 必须一层层点开, 很繁琐
- 如果console.log过多, 手动一个个删除非常麻烦, 而且容易出错
- ...
针对以上问题的痛点, 我做了以下的的优化
- 提供全局方法, 分别打印基础变量和ref变量, 并自动解析ref值和打印ref变量(利用isRef, unref, toRaw)
- 判断传入值的类型, 如果是对象类型, 做对象类型的打印, 否则做基础数据类型打印
- 提供打印变色, 字体变大, 显示当前打印所在文件和行, vscode的代码片段功能可以提供这些能力
- ...
一步步来, 先说下我的整体思路
- 创建公共方法log, 在公共方法里, 传递三个变量, 分别是要打印的变量名, 变量值, 以及其他所有你想要显示在控制台上的信息
- 在公共方法里, 通过isRef方法, 判断是否是ref值. 如果是ref值, 使用unref方法, 自动解析, 那么你传值的时候, 加不加.value都可以, 然后利用toRaw, 打印出值; 如果是非ref值, 则直接打印纸. 注意, 两者因为只是打印变量不一样, 所以又抽出去了个公共方法_log
- 因为想真的对象类型, 做特殊打印, 所以又加了个对象判断, 如果是对象, 打印成JSON.stringify解析后的值
- 在main.js中引入并挂载到全局
- 然后直接使用 proxy.log(`obj`, obj)
实现代码
- src/utils/gFunc.js
js
import { isRef, toRaw, unref } from 'vue'
export function log(variableStr, variable) {
if (isRef(variable)) {
let unrefVariable = unref(variable)
_log(toRaw(unrefVariable))
} else {
_log(variable)
}
function _log(consoleData) {
if (judgeType(consoleData) === 'object') {
console.log(
`%c${variableStr}`,
'background:#fff; color: blue;font-size: 1.2em',
JSON.stringify(consoleData, null, '\t'),
)
} else {
console.log(
`%c${variableStr}`,
'background:#fff; color: blue;font-size: 1.2em',
consoleData,
)
}
}
function judgeType(type) {
if (typeof type === 'object') {
const objType = Object.prototype.toString
.call(type)
.slice(8, -1)
.toLowerCase()
return objType
} else {
return typeof type
}
}
}
- 在main.js中引入
js
import { log } from '@/utils/gFunc.js'
app.config.globalProperties.log = log;
- 在任意.vue文件中测试代码是否能正常打印
js
<script setup lang="ts">
import { ref, getCurrentInstance, toRaw } from 'vue'
const { proxy } = getCurrentInstance()
let o1 = {
name: 'andy',
arr: {
age: 18,
height: 1.88,
},
}
proxy.log(`o1`, o1)
let a1 = [1, 2, 3, [4.1, 4.2]]
proxy.log(`a1`, a1)
let s1 = '生当作人杰, 死亦为鬼雄'
proxy.log(`s1`, s1)
let n1 = null
proxy.log(`n1`, n1)
let b1 = true
proxy.log(`b1`, b1)
let u1 = undefined
proxy.log(`u1`, u1)
let or1 = ref({
name: 'andy',
arr: {
age: 18,
height: 1.88,
},
})
proxy.log(`or1`, or1)
let ar1 = ref([1, 2, 3, [4.1, 4.2]])
proxy.log(`ar1`, ar1)
let sr1 = ref('生当作人杰, 死亦为鬼雄')
proxy.log(`sr1`, sr1)
let nr1 = ref(null)
proxy.log(`nr1`, nr1)
let br1 = ref(true)
proxy.log(`br1`, br1)
let ur1 = ref(undefined)
proxy.log(`ur1`, ur1)
</script>
- 看结果
2. 进阶功能
如果只是以上一个公共函数, 还很难实现0.1秒就打印console, 虽然以上的东西已经可以省去很大一部分时间, 但是我们想要实现的是最优解, 所以要更近一步实现进阶功能, 进阶功能需要用到一个插件和snippet功能, 我所使用的编辑器是vscode, 不同编辑器之间的用法可能有出入, 但是实现逻辑, 都是差不多的, 如果你用的是其他编辑器, 请自行修改对应的参数
注意:
- 我的电脑是mac, 如果是windows, 对应的快捷键会有部分出入, 主要是mac的command对应的是windows的ctrl, mad的ctrl对应windows的alt
- 我认为下面的快捷键作为, 最常用的快捷键, 设为ctrl+z, ctrl+x, ctrl+c这种离手最近的位置最好, 不过我那几个快捷键已经被占用了, 所以用了下面的. 以下快捷键, 可以根据个人习惯去调整
进阶思路 设想一下, 如果我想打印一个变量, 那么一个字一个字的去敲, 绝对不是最佳方案. 不卖关子了, 最佳方案, 我认为是 选中这个变量, 然后直接在下一行打印这个变量, 并把你想要输出的信息都在下一行显示出来. 或者直接在任何一行通过快捷键, 把你想要写的带有各种打印信息的代码, 直接写入在编辑器中. 那么这里边就有几个特别重要的点需要大家考虑
- 快捷键的方案实现
- 想要打印的数据, 如何直接写入编辑器中
- 如何获取想要打印的数据, 比如当前文件夹, 当前第几行, 当前打印的变量值
下面说实现方案
2.1 通过快捷键ctrl+s, 将完整数据打印在页面中
实现步骤
- 在vscode中, 按cmd+shift+p打开搜索文件的窗口, 输入>open keyboard shortcuts, 回车进入快捷键keybindings.json文件
- 插入快捷键, 解释下以下代码, 将剪切版的值作为初始化变量插入到下面这个代码片段中, 并且将代码行数和相对路径作为第三个参数, 放在proxy.log这个函数里
js
{
"key": "ctrl+s", // 打印proxy.log
"command": "editor.action.insertSnippet",
"when": "editorTextFocus",
"args": {
"snippet": "proxy.log(`${1:${CLIPBOARD}}`, ${1:${CLIPBOARD}}, \"${TM_LINE_NUMBER}行 ${RELATIVE_FILEPATH}\");"
}
},
- 简单修改下log函数, 注意: 我只写了修改后的代码, 其实就是加了个 参数, 这是可以适配之前的函数
js
log(variableStr, variable) 变味了 log(variableStr, variable, otherInfo = '')
console.log(
`%c${variableStr}` 变为了 console.log(
`%c${variableStr} ${otherInfo}`
- 页面中测试, 按下 ctrl + s, 打印下面的数据
js
const str = ref(11)
proxy.log(`str`, str, '4行 src/views/test/t3.vue')
2.2 选中快捷键打印的实现方案(ctrl+a), 也是我工作中最常用的方案
假如你想打印一个变量在他的下一行, 我们可以想象的操作是, 先选中变量, 然后复制, 然后按cmd+回车, 然后使用上一步封装的ctrl+s快捷键将其打印在 这一行. 我们可以想一下, 选中变量后, 复制, cmd+回车, 然后写入代码
这些操作, 是不是都可以通过工具去实现呢? 那么最终的方案是我们只需要选中变量, 然后快捷键, 就能一键写入代码了. 2步操作就能打印, 我想不到更精简的操作了. 除非是能自动选中, 可惜我还没找到这个方案. 这个功能依赖插件 multi-command
, 需要先安装
实现步骤
- 在vscode中, 按cmd+shift+p打开搜索文件的窗口, 输入>open keyboard shortcuts, 回车进入快捷键keybindings.json文件
- 插入快捷键, 解释下以下代码, 执行multiCommand里的命令, 这个命令是addVue3Console
js
{
"key": "ctrl+a", // 快速打印console
"command": "extension.multiCommand.execute",
"args": {
"command": "multiCommand.addVue3Console"
},
"when": "editorTextFocus"
},
- 在vscode中, 按cmd+shift+p打开搜索文件的窗口, 输入>open user setting, 回车进入快捷键
打开用户设置(JSON)
文件 - 插入代码片段, 解释下代码. 命令addVue3Console, 会依次执行以下命令, 首先copy选中的值, 然后插入下一行, 然后写入代码片段, 注意, 这里, src/views我有进行replace替换, 因为大部分文件都在这个文件夹下, 不用写也知道, 所以替换掉省的看着难受
js
{
"command": "multiCommand.addVue3Console",
"sequence": [
{
"command": "execCopy",
"when": "editorFocus && textInputFocus && terminalTextSelected"
},
{
"command": "editor.action.insertLineAfter",
"when": "editorTextFocus && !editorReadonly && terminalTextSelected"
},
{
"command": "editor.action.insertSnippet",
"when": "!editorTextFocus",
"args": {
"snippet": "proxy.log(`${1:${CLIPBOARD}}`, ${1:${CLIPBOARD}}, \"${TM_LINE_NUMBER}行 ${RELATIVE_FILEPATH/src\\/views\\///}\");"
}
},
]
},
2.3 使用代码提示功能快速打印
有时候, 不想用快捷键咋办? 虽然我还是建议使用快捷键, 但是snippets又不光只支持快捷键. 代码提示功能也很好用很强大啊, 下面写一个代码片段, 实现以上打印功能, 我们给这个快捷键触发的字符串 设为cc
, 按下cc就能直接打印代码片段, 有一个好用的生成快捷键地址 快捷键转换地址
- 在vscode中, 按cmd+shift+p打开搜索文件的窗口, 输入>config user snippets, 回车进入snippets: Config User Snippets文件, 选一个文件进去配置代码片段
- 插入代码片段
js
"cc": {
"scope": "javascript,typescript",
"prefix": "cc简写",
"body": [
"proxy.log(`${1:${CLIPBOARD}}`, ${1:${CLIPBOARD}}, \"${TM_LINE_NUMBER}行 ${RELATIVE_FILEPATH/src\\/views\\///}\");"
],
},
大功告成,下面看最终测试代码
js
<script setup lang="ts">
import { ref, getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
let o1 = {
name: 'andy',
arr: {
age: 18,
height: 1.88,
},
}
proxy.log(`o1`, o1, '7行 test/t3.vue')
let a1 = [1, 2, 3, [4.1, 4.2]]
proxy.log(`a1`, a1, '15行 test/t3.vue')
let s1 = '生当作人杰, 死亦为鬼雄'
proxy.log(`s1`, s1, '19行 test/t3.vue')
let n1 = null
proxy.log(`n1`, n1, '21行 test/t3.vue')
let b1 = true
proxy.log(`b1`, b1, '23行 test/t3.vue')
let u1 = undefined
proxy.log(`b1`, b1, '25行 test/t3.vue')
let or1 = ref({
name: 'andy',
arr: {
age: 18,
height: 1.88,
},
})
proxy.log(`or1`, or1, '28行 test/t3.vue')
let ar1 = ref([1, 2, 3, [4.1, 4.2]])
proxy.log(`ar1`, ar1, '36行 test/t3.vue')
let sr1 = ref('生当作人杰, 死亦为鬼雄')
proxy.log(`sr1`, sr1, '39行 test/t3.vue')
let nr1 = ref(null)
proxy.log(`nr1`, nr1, '41行 test/t3.vue')
let br1 = ref(true)
proxy.log(`br1`, br1, '43行 test/t3.vue')
let ur1 = ref(undefined)
proxy.log(`ur1`, ur1, '45行 test/t3.vue')
</script>
3. 拓展
3.1 建议将log函数放在组件库中
如果在各个前端项目中, 每次写, 每次引的很麻烦, 建议将这个函数放在现有的前端组件库中. 比如我们公司我就新建了个前端组件库, 里边我把函数都暴漏出去了. 那我每次只需要引入组件库, 然后把组件库里暴漏的函数, 在遍历挂载到项目全局中使用即可, 我以我在组件库中的使用为例
具体步骤
- 在个人组件库中
oeos-v3-components
的packages/utils文件夹下, 新建gFunc.js, 把公共函数都写在这里. - 在packages/index.js中, 引入并暴漏所有gFunc.js下的函数
js
// 注意这里, registerDirectives, finalComps是对应的 自定义指令和组件, 因为不涉及这块, 所以没有写, 只是想告诉大家, 在package/index下, 我也暴漏了组件和自定义指令, 而不是单独把函数暴漏了出去
const install = (app) => {
registerDirectives(app)
finalComps.forEach((v, i) => {
app.component(v, comps[i])
})
}
export { utils }
export * from './utils/gFunc.js'
export default {
finalComps,
install,
utils,
}
- 在前端项目中引入并挂载全局, main.js中写以下代码
js
import App from './App.vue'
const app = createApp(App)
import oeosV3Components, { utils } from 'oeos-v3-components'
// 将oeos-v3-components下的公共函数赋值到全局
Object.keys(utils).forEach((v) => {
app.config.globalProperties[v] = utils[v]
})
app.use(oeosV3Components)
- 在.vue文件中使用
proxy.log(
str, str, "5行 src/views/test/t3.vue");
- 在js文件中使用
js
import { log } from 'oeos-v3-components'
log(`11`, 11, "2行 test/e1.js");
3.2 一键删除 console.log 和 debugger 和 proxy.log
如果一个文件中, 有很多console.log, proxy.log, debugger. 如果手动清理, 势必会非常麻烦, 而且容易出错. 那么用软件清理就没这些问题了. 推荐Keyboard Maestro Editor. 这在国内用的人不多, 原理就是, 快捷键打开vscode中的替换, 然后清空里边的数据, 然后写入匹配console.log的正则, 在快捷键替换; 然后再替换proxy.log, 依次类推. 主要是这个正则[\n\s\r\t\/]*console.log\(([^)]|\\n)*\).*
, 放个图吧, 只是我个人这么用. 我想实现我上诉所说的功能, 所以用了这个软件, 我个人用的比较多. 如果有更通用的方案, 也可以讨论下
4. 题外话.
虽然这些功能, 看似没有太多复杂的东西. 但是却是我好几年, 不断总结琢磨出来的. 我有看技术类视频的习惯, 不过大部分的视频, 都是讲的前端知识点, 对这些我认为可以提高个人开发效率的功能, 讲的比较少. 所以很多地方, 都是我针对个人的痛点, 去想办法解决的, 思路不见得是最好, 但是确是我目前能想到的比较好的解决方案了. 如果有更好的解决思路, 可以一起探讨