Chrome 监听 console 打开

这个算是 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 = "";
        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 也会执行。
如果只是监听控制台打开,这个几行代码足以,但是我还没想到监听控制台关闭方法。

这么简单的代码,我就不写成插件装逼了,需要的时候直接用即可。在 runjs 上写了个 demo,打开预览下效果吧!
预览: http://sandbox.runjs.cn/show/vjtgjbzg

后序

控制台环境下有很多功能都很方便很好用,多读读会发现很多神奇的技巧。

本文同步至我的个人博客:http://www.52cik.com/2016/04/27/chrome-console-open.html

你可能感兴趣的:(Chrome 监听 console 打开)