关于vConsole 源码的理解分享(vConsole一个移动端调试控制台工具)(1)

2023-12-20 01:58

本文主要是介绍关于vConsole 源码的理解分享(vConsole一个移动端调试控制台工具)(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一个轻量、可拓展、针对手机网页的前端开发者调试面板。

特性

  • 查看 console 日志
  • 查看网络请求
  • 查看页面 element 结构
  • 查看 Cookies 和 localStorage
  • 手动执行 JS 命令行
  • 自定义插件

这是github的readme介绍,对于调试移动端以及上线后出现的一些问题确实是一个很好的工具!! 为腾讯开源点个赞。
使用方法请参考:腾讯开源vConsle移动端调试控制台工具 (请原谅我把它叫的那么长哈哈哈)

源码分析

总的来说,vConsole的源码还是很清晰的,采用了es6的语法,以面向对象的形式即类的显示来组织代码,按照控制台的ui来分割,这一点值得借鉴和学习。

源码代码结构

VConsole的类结构图

简单的画了一个草图,首先要明确是 VConsole为核心类,负责插件的生成,事件绑定,运行等,而vConsolePlugin 类似一个抽象类,具体实现渲染绑定等由子类去具体实现。所以VConsole的扩展是通过添加VConsolePlugin的实现来的。(如果有歧义请指正批评)

模板引擎

vConsole采用的是一个叫做 Mito.js 的 一个简单的模板引擎(估计是腾讯自己内部写的一个模板引擎),这个文件在lib文件夹下,代码很简短就一个rener方法,然后配合模板html使用

//使用指定数据将模板文本编译成 element 对象或者 HTML 字符串。
//(required) tpl: 模板字符串。
//(required) data: 一组 key-value 形式的数据源。
//(optional) toString: 布尔值,用于设定返回值为 element 对象还是 HTML 字符串,默认为 `false`。
//返回值 Element 对象或者 HTML 字符串
function render(tpl, data, toString)

模板语法涉及到了常用的if else ,for和switch 常用的流程语句,对付一些简单的需求这个已经足够了,详细语法使用参考官方文档的helper_function.md

核心模块
核心模块主要是core.js的这个js文件,也就是VConsole类的定义 。

首先来看下这个类的Construction方法,我把注释写在代码里 ,这样看起来 应该很清晰了

constructor(opt) {//判断是否已经存在VConsole实例了,通过判断document文档结构是否存在了VCONSOLE_ID即(#__vconsole) 这个id。if (!!$.one(VCONSOLE_ID)) {console.debug('vConsole is already exists.');return;}//将this用that保存起来,这里的作用不言而喻,就是为了防止接下来的操作(方法)调用会改变this的指向let that = this;//版本号this.version = pkg.version;//基础的dom 可以理解为root  即id为VCONSOLE_ID的文档元素this.$dom = null;//再次判断是否已经初始化的标识,和一开始的方法一样的作用this.isInited = false;//配置选项 以下是对加载哪几个默认插件的配置this.option = {defaultPlugins: ['system', 'network', 'element', 'storage']};//当前活动的tab页框this.activedTab = '';//tab页框数组this.tabList = [];//插件列表this.pluginList = {};//vConsole的悬浮按钮的配置(位置)this.switchPos = {x: 10, // righty: 10, // bottomstartX: 0,startY: 0,endX: 0,endY: 0};// 暴露一些公用的方法出去,比如判断是否对象,数组等  详见在../lib/tool.js的文件this.tool = tool;//暴露$的方法,query.js文件夹this.$ = $;// 合并用户配置和默认配置。for in 用法了解下if (tool.isObject(opt)) {for (let key in opt) {this.option[key] = opt[key];}}//初始化添加插件 插件的初始化 添加到tabListthis._addBuiltInPlugins();// Vconsole的UI加载,let _onload = function() {if (that.isInited) {return;}//渲染VConsole的主面板,swicth按钮that._render();//面板上的交互 模拟 触摸滑动that._mockTap();//绑定事件 拖动swicth按钮,点击面板的tab等that._bindEvent();//面板渲染完成后的插件自动加载渲染that._autoRun();};//当document渲染加载完成  调用_onload方法if (document !== undefined) {if (document.readyState == 'complete') {_onload();} else {$.bind(window, 'load', _onload);}} else {// if document does not exist, wait for itlet _timer;let _pollingDocument = function() {if (!!document && document.readyState == 'complete') {_timer && clearTimeout(_timer);_onload();} else {_timer = setTimeout(_pollingDocument, 1);}};_timer = setTimeout(_pollingDocument, 1);}}

总的流程用流程图表示

Created with Raphaël 2.1.2 开始 初始化一些参数:tablist,Pluginlist,版本号等 加载初始化配置的插件 是否加载document完成 VConsole加载UI,绑定事件,运行插件等 结束 等待document加载完 yes no

大概VConsole类的流程和做的一些事情都阐述完了,接下来分析一些重要的方法

addPlugin();

添加一个新的插件,这个为基本组件如何加到VConsole的公用方法,实现加入到VConsole体系,并且运行起来。
/*** add a new plugin* @public* @param object VConsolePlugin object* @return boolean*/addPlugin(plugin) {// ignore this plugin if it has already been installedif (this.pluginList[plugin.id] !== undefined) {console.debug('Plugin ' + plugin.id + ' has already been added.');return false;}this.pluginList[plugin.id] = plugin;// init plugin only if vConsole is readyif (this.isInited) {this._initPlugin(plugin);// if it's the first plugin, show it by defaultif (this.tabList.length == 1) {this.showTab(this.tabList[0]);}}return true;}

而在这个方法中最重要的方法就是下面这个方法_initPlugin();初始化插件,包括加入panel中的点击事件等一些操作,其中的insertAdjacentElement()方法相当于jq中的insertAfter和insertBefore等方法

/*** init a plugin* @private*/_initPlugin(plugin) {let that = this;plugin.vConsole = this;// start initplugin.trigger('init');// render tab (if it is a tab plugin then it should has tab-related events)plugin.trigger('renderTab', function(tabboxHTML) {// add to tabListthat.tabList.push(plugin.id);// render tabbarlet $tabbar = $.render(tplTabbar, { id: plugin.id, name: plugin.name });$.one('.vc-tabbar', that.$dom).insertAdjacentElement('beforeend', $tabbar);// render tabboxlet $tabbox = $.render(tplTabbox, { id: plugin.id });if (!!tabboxHTML) {if (tool.isString(tabboxHTML)) {$tabbox.innerHTML += tabboxHTML;} else if (tool.isFunction(tabboxHTML.appendTo)) {tabboxHTML.appendTo($tabbox);} else if (tool.isElement(tabboxHTML)) {$tabbox.insertAdjacentElement('beforeend', tabboxHTML);}}$.one('.vc-content', that.$dom).insertAdjacentElement('beforeend', $tabbox);});// render top barplugin.trigger('addTopBar', function(btnList) {if (!btnList) {return;}let $topbar = $.one('.vc-topbar', that.$dom);for (let i = 0; i < btnList.length; i++) {let item = btnList[i];let $item = $.render(tplTopBarItem, {name: item.name || 'Undefined',className: item.className || '',pluginID: plugin.id});if (item.data) {for (let k in item.data) {$item.dataset[k] = item.data[k];}}if (tool.isFunction(item.onClick)) {$.bind($item, 'click', function(e) {let enable = item.onClick.call($item);if (enable === false) {// do nothing} else {$.removeClass($.all('.vc-topbar-' + plugin.id), 'vc-actived');$.addClass($item, 'vc-actived');}});}$topbar.insertAdjacentElement('beforeend', $item);}});// render tool barplugin.trigger('addTool', function(toolList) {if (!toolList) {return;}let $defaultBtn = $.one('.vc-tool-last', that.$dom);for (let i = 0; i < toolList.length; i++) {let item = toolList[i];let $item = $.render(tplToolItem, {name: item.name || 'Undefined',pluginID: plugin.id});if (item.global == true) {$.addClass($item, 'vc-global-tool');}if (tool.isFunction(item.onClick)) {$.bind($item, 'click', function(e) {item.onClick.call($item);});}$defaultBtn.parentNode.insertBefore($item, $defaultBtn);}});// end initplugin.isReady = true;plugin.trigger('ready');}

这个函数是初始化插件,例如日志模块,网络模块都是以插件的形式,集成进来的,既然是插件,,那就要有插槽,自然,插件提供插口。这个函数就是一个插槽,当插件集成进来的时候,就通过触发插槽里的插口来实现调用插件的方法,这个有五个个触发的方法,插件中实现了就会触发

  • init 初始化插件
  • renderTab 渲染tab 基础的如log system这个
  • addTopBar 渲染 bar栏 tab下的子栏目
  • addTool 添加工具方法 如clear hide
  • ready 插件准备完毕,开始输出 log 等
VConsolePlugin 这个抽象类做了什么
class VConsolePlugin {constructor(id, name = 'newPlugin') {//id 为log system 来标识插件this.id = id;this.name = name;//组件的准备状态  在VConsole的初始化完插件 设置为true,即组件渲染完成this.isReady = false;//事件列表this.eventList = {};}get id() {return this._id;}set id(value) {if (!value) {throw 'Plugin ID cannot be empty';}this._id = value.toLowerCase();}get name() {return this._name;}set name(value) {if (!value) {throw 'Plugin name cannot be empty';}this._name = value;}//获取VConsole实例get vConsole() {return this._vConsole || undefined;}set vConsole(value) {if (!value) {throw 'vConsole cannot be empty';}this._vConsole = value;}/*** register an event* @public* @param string* @param function*/on(eventName, callback) {this.eventList[eventName] = callback;return this;}/*** trigger an event* @public* @param string* @param mixed*/trigger(eventName, data) {if (typeof this.eventList[eventName] === 'function') {// registered by `.on()` methodthis.eventList[eventName].call(this, data);} else {// registered by `.onXxx()` methodlet method = 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);if (typeof this[method] === 'function') {this[method].call(this, data);}}return this;}} // END class

其实也没做什么事,但是有一个很重要的trigger方法

/*** trigger an event* @public* @param string* @param mixed*/trigger(eventName, data) {if (typeof this.eventList[eventName] === 'function') {// registered by `.on()` methodthis.eventList[eventName].call(this, data);} else {// registered by `.onXxx()` methodlet method = 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);if (typeof this[method] === 'function') {this[method].call(this, data);}}return this;}

这样一来,只要每个插件的方法 以 on 开头 ,就可以通过基类触发方法的执行了,具体的逻辑是先去找 on注册的,如果没有 就去实例中找,还有一点这个方法是返回对象本身的。

这篇关于关于vConsole 源码的理解分享(vConsole一个移动端调试控制台工具)(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/514446

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get