这个算是 Chrome only 其他的我没测试,也不想测试。因为我的控制台脚本仅仅在 Chrome 下加载。
如果你需要全平台,那么这肯定不是你需要的结果。
需求
其实我很早就想折腾这个了,但是,,因为懒,拖了很久,直到周末,我看到服务器上统计,发现流量翻了一倍,结果访问量还是一样的时候,我才下决心折腾。
知之为知之不知谷歌之
一开始,谷歌一番,发现有两种思路。
第一个是 sindresorhus 大神写的 devtools-detect,算是全平台兼容(除IE),但独立窗口打开的时候是检测不到的。
另一个是咱们国人 zswang 写的 jdetects,目测也是 Chrome only,当然我的灵感也来至于他。
虽然有两个现成的,但这都不是我满意的模式,于是乎就有了本次 Chrome 控制台环境探索之旅。
分析控制台环境
根据 zswang 的 jdetects 得知,控制台会解析节点元素的 id 属性。
那么为什么会解析呢?或者他还做了什么处理呢?
打开浏览器,按 F12 打开 console 后输入 debugger 按回车,然后按两次 F11,OK 完成。
如果你的 Chrome 50 的话,映入眼帘的是密密麻麻的一大串压缩的字符,好在他们没 uglify,否则我就默默关了,也不会有这篇文章了。
点左下角 {}
格式化代码后,变的非常漂亮,但是没有注释了,我记得之前版本都是有注释的,更容易阅读。
大致预览下代码,最终定位到 660 行的 _describe
方法处,其他都不管我也不知道干嘛的,分析需要的代码即可。
// 用易懂的形式,描述各种对象方法,如正则,日期,node,数组,函数 等。
_describe: function(obj) {
if (this.isPrimitiveValue(obj)) // 如果是原始值不描述
return null;
// 获取类型名包括 ArrayLike,但不是 Object.prototype.toString,有兴趣可以单独查看源码
var subtype = this._subtype(obj);
if (subtype === "regexp") // 正则和日期输出 toString 后的结果。
return toString(obj);
if (subtype === "date")
return toString(obj);
if (subtype === "node") { // dom 节点处理,这里是重点
// 获取节点名,text comment 等只有 nodeName
var description = obj.nodeName.toLowerCase();
switch (obj.nodeType) { // 节点类型
case 1: // Element 类型
description += obj.id ? "#" + obj.id : ""; // 获取元素 id
var className = obj.className; // 获取元素 class
description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
break;
case 10: // DocumentType 类型
description = "<!DOCTYPE " + description + ">";
break;
}
return description;
}
// 获取内部构造函数名,可能类似 Object.prototype.toString
var className = InjectedScriptHost.internalConstructorName(obj);
// 类似数组的就输出 对象名[长度] 比如 Array[3], jQuery.fn.jQuery.init[2] 之类的
if (subtype === "array") {
if (typeof obj.length === "number")
className += "[" + obj.length + "]";
return className;
}
if (typeof obj === "function") // 函数 toString
return toString(obj);
if (isSymbol(obj)) { // Symbol 处理
try {
return (InjectedScriptHost.callFunction(Symbol.prototype.toString, obj)) || "Symbol";
} catch (e) {
return "Symbol";
}
}
// 错误类型处理
if (InjectedScriptHost.subtype(obj) === "error") {
try {
var stack = obj.stack;
var message = obj.message && obj.message.length ? ": " + obj.message : "";
var firstCallFrame = /^\s+at\s/m.exec(stack);
var stackMessageEnd = firstCallFrame ? firstCallFrame.index : -1;
if (stackMessageEnd !== -1) {
var stackTrace = stack.substr(stackMessageEnd);
return className + message + "\n" + stackTrace;
}
return className + message;
} catch (e) {}
}
return className;
}
OK,代码挺简单的,看完基本就知道为什么 jdetects 可以检测控制台是否被打开了。
那么现在我们知道,其实完全可以借助 正则,日期,函数
的 toString
实现,而且更简单,例如:
var re = /x/;
var i = 0;
console.log(re);
re.toString = function () {
return '第 ' + (++i) + ' 次打开控制台';
};
简单又好用,也不需要定时器或 resize
事件监视,性能更是好到不用说。需要注意的是这里的 re.toString
必须在 console.log
之后定义,否则没打开控制台 toString
也会执行。
如果只是监听控制台打开,这个几行代码足以,但是我还没想到监听控制台关闭方法。