《JavaScript高等程序设计》(第3版)读书笔记 第9章 客户端检测

  • 检测Web客户端的手腕许多,各有利弊,但不到万不得已就不要运用客户端检测。只需能找到更通用的要领,就应当优先采纳更通用的要领。一言蔽之,先设想最通用的计划,然后再运用特定于浏览器的手艺加强计划

才能检测

  • 才能检测(又称特征检测),是普遍为人接收的客户端检测情势,目标不是辨认特定的浏览器,而是辨认浏览器的才能。
  • IE5.0之前的版本不支撑document.getElementById()这个DOM要领,只管可以运用非标准的document.all属性完成雷同的目标。因而就有相似下面的才能检测代码
function getElement(id) {
  if (document.getElementById) {
    return document.getElementById(id);
  } else if (document.all) {
    return document.all[id];
  } else {
    throw new Error("No way to retrieve element!");
  }
}
  • 一个特征存在,不一定另一个特征也存在
function getWindowWidth() {
  if (document.all) {
    // 假定这里是IE浏览器
    return document.documentElement.clientWidth;     // 毛病的用法
  } else {
    return window.innerWidth;
  }
}

更牢靠的才能检测

// 这不是才能检测——只是检测了是不是存在相应的要领
function isSortable(object) {
  return !!object.sort;
}

// 任何包含sort属性的对象都邑返回true
var result = isSortable({sort: true});
// 如许更好:检测sort是不是是函数
function isSortable(object) {
  return typeof object.sort == "function";
}
  • 在可以的情况下,只管运用typeof操纵符举行才能检测。特别是,宿主对象没有义务让typeof返回合理的值。最怒不可遏的事就发生在 IE 中。大多数浏览器在检测到document.createElement()存在时,都邑返回true
// 在IE8及之前版本不可
function hasCreateElement() {
  return typeof document.createElement == "function";
}
  • IE8- 中这个函数返回false,因为typeof document.createElement返回的的是“object”,而不是“function”。如前所述,DOM对象是宿主对象,IE及更早版本中的宿主对象是经由过程COM而非JScript完成的。因而,document.createElement()函数确实是一个COM对象。IE9改正了这个题目,对一切DOM要领都返回”function”。

才能检测,不是浏览器检测

// 还不够详细
var isFirefox = !!(navigator.vendor && navigator.vendorSub);

// 假定过甚了
var isIE = !!(document.all && document.uniqueID);
  • 检测某个或几个属性并不可以肯定浏览器。navigator.vendornavigator.vendorSub确实是Firefox的独占属性,然则厥后Safari也依样画葫芦完成了雷同的属性。document.all && document.uniqueID这两个属性是初期IE的独占属性,如今还存在,但不保证未来IE不会去掉。
  • 根据浏览器差别将才能组合起来是更可取的体式格局。假如你晓得本身的应用程序须要运用某些特定的浏览器特征,那末最好是一次性检测一切相干特征,而不是离别检测。
// 肯定浏览器是不是支撑 Netscape作风的插件
var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);

// 肯定浏览器是不是具有DOM1级划定的才能
var hasDOM1 = !!(document.getElementById && document.createElement && document.getElementsByTagName);
  • 在现实开辟中,应当将才能检测作为肯定下一步解决计划的根据,而不是用它来推断用户运用的是什么浏览器。

怪癖检测

  • 怪癖检测 (quirks detection) 的目标是辨认浏览器的特别行动。但与才能检测差别,怪癖检测是想晓得浏览器存在什么缺点(也就是bug)。
  • 比方IE8及之前版本存在一个bug,即假如某个实例属性与[[Enumerbale]]标记为false的某个原型属性同名,那末该实例将不会出如今for-in循环中。
// 检测上述怪癖的代码
var hasDontEnumQuirk = function() {

  var o = { toString: function() {} };
  for (var prop in o) {
    if (prop == "toString") {
      return false;
    }
  }
  return true;
}
  • 另一个常常须要检测的怪癖是Safari 3 之前版本会罗列被隐蔽的属性。
var hasEnumShadowsQuirk = function() {

  var o = { toString: function() {} };
  var count = 0;
  for (var prop in o) {
    if (prop == "toString") {
      count++;
    }
  }
  // 假如浏览器存在这个bug
  // 就会返回两个 toString 的实例
  return (count > 1);
}
  • 因为检测怪癖触及运转代码,因而发起仅检测那些对你有直接影响的怪癖,而且最幸亏剧本一最先就实行此类检测。

用户代办检测

  • 用户代办检测是争议最大的客户端检测手艺。
  • 用户代办检测经由过程用户代办字符串来肯定现实运用的浏览器。在每一次HTTP要求过程当中,用户代办字符串是作为相应首部发送的,而且该字符串可以经由过程JavaScript的navigator.userAgent属性接见。
  • 在服务端,经由过程检测用户代办字符串来肯定用户运用的浏览器是一种经常使用的而且广为接收的做法。而在客户端,用户代办检测平常被当作一种万不得已采纳的做法,其优先级排在才能检测和怪癖检测今后。
  • 有关的争议不得不提电子诳骗(spoofing)。浏览器经由过程在本身的用户代办字符串到场一些毛病或误导信息,来到达诳骗服务器的目标。

用户代办字符串的汗青

用户代办字符串检测手艺

辨认显现引擎

  • 确实的纸袋浏览器的名字和版本不如确实的纸袋它运用的是什么引擎。
  • 我们要编写剧本将五大显现引擎:IE, Gecko, WebKit, KHTML, Opera
  • 为了不在全局作用域中增加过剩变量,我们将运用模块加强形式来封装检测剧本
var client = function() {
  
  var engine = {

    // 显现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 详细的版本号
    ver: null
  };

  // 在此检测显现引擎、平台和装备
  ...

  return {
    engine: engine
  };
}();
  • 匿名函数内定义了一个局部变量engin,包含默认设置的对象字面量,每一个显现引擎都对应着一个属性,默认值为0.假如检测到了哪一个显现引擎,那末就以浮点数值情势,将引擎的版本号写入相应的属性。而显现引擎的完全版本(一个字符串)则被写入 ver 属性。
if (client.engine.ie) {
  // 假如是IE client.ie 应当大于0
  ...
} else if (client.engine.gecko > 1.5) {
  if (client.engine.ver === "1.8.1") {
    // 针对这个版本的操纵
    ...
  }
}
  • 第一个要检测是Opera,我们不相信Opera,是因为其用户代办字符串不会将本身标识为Opera。要辨认Opera,必需检测window.opera对象。Opera5+都有这个版本。在Opera7.6+中挪用version()要领可以返回一个示意浏览器版本的字符串。
if (window.opera) {
  engine.ver = window.opera.version();
  engine.opera = parseFloat(engine.ver);
}
  • 第二个要检测是Webkit。因为WebKit用户代办字符串中包含”Gecko”和”KHTML”这两个子字符串,所以假如起首检测它们可以会得出毛病的结论。不过“AppleWebKit”是举世无双的。
  • iPhone 6s Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
  • Chrome 74 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
  • 因为现实的版本号可以会包含数字、小数点和字母,所以捕捉组中运用了示意非空格的特别字符 \S 。用户代办字符串中的版本号与下一部份的分开是一个空格,因而这个形式可以保证捕捉一切版本信息。
var ua = navigator.userAgent;

if (/AppleWebKit\/(\S+)/.test(ua)) {
  // \S 示意非空格的特别字符
  // \S+ 示意不包含空格的子字符串
  // 小括号示意将此子字符串到场捕捉组
  // RegExp["$1"] 示意捕捉组的第一个元素,即为上面形貌的子字符串
  engine.ver = RegExp["$1"];
  engine.webkit = parseFloat(engine.ver);
}
  • 接下来要测试的是KHTML。一样,字符串中也包含“Gecko”,因而在消除KHTML之前,我们没法准确检测基于GECKO的浏览器。花样与Webkit差不多。另外因为Konqueror3.1及更早版本中不包含KHTML的版本,故而就要运用Konqueror替代。
if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.khtml = parseFloat(engine.ver);
}
  • 下面检测Gecko。版本号不在Gecko背面,而是在 “rv:”背面。比方WindowsXP 下的Firefox2.0.0.11:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
// 在上面的字符串中现实婚配的是"rv:1.8.1.11) Gecko/20071127"
// gecko的版本号位于 rv: 与一个闭括号之间,因而为了提掏出这个版本号
// [^\)]+ 就是将 rv: 今后的字符串消除 ) 闭括号 今后到场捕捉组
// 正则表达式要查找一切不是闭括号的字符,还要查找字符串"Gecko/"后跟8个数字
if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.gecko = parseFloat(engine.ver);
}
  • 末了一个检测IE。IE的版本号位于字符串”MSIE”的背面、一个分号的前面
// 五个显现引擎完全的检测代码以下
var ua = navigator.userAgent;

if (window.opera) {
  engine.ver = window.opera.version();
  engine.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.webkit = parseFloat(engine.ver);
} else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.khtml = parseFloat(engine.ver);
} else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.gecko = parseFloat(engine.ver);
} else if (/MSIE ([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.ie = parseFloat(engine.ver);
}

辨认浏览器

  • 辨认显现引擎大多数情况下足认为我们采用准确的操纵供应了根据(pc上,挪动端大部份都不可)。
  • 苹果公司的Safari浏览器和谷歌的Chrome浏览器都是用Webkit作为显现引擎,但它们的JavaScript引擎却差别。
// 我们的检测代码须要增加浏览器的检测
var client = function() {
  
  var engine = {

    // 显现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 详细的版本号
    ver: null
  };

  var browser = {

    // 浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,

    // 详细的版本号
    ver: null
  }

  // 在此检测显现引擎、平台和装备
  ...

  return {
    engine: engine,
    browser: browser
  };
}();
  • 因为大多数浏览器与其显现引擎密切相干,所以下面的检测浏览器代码和检测显现引擎的代码是夹杂在一起的。
var ua = navigator.userAgent;

if (window.opera) {
  engine.ver = browser.ver = window.opera.version();
  engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit\/(\S+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.webkit = parseFloat(engine.ver);

  // 肯定是Chrome照样Safari
  if (/Chrome\/(\S+)/.test(ua)) {
    browser.ver = RegExp["$1"];
    browser.chrome = parseFloat(browser.ver);
  } else if (/Version\/(\S+)/.test(ua)) {
    browser.ver = RegExp["$1"];
    browser.safari = parseFloat(browser.ver);
  } else {
    // 近似的肯定版本号
    var safariVersion = 1;
    if (engine.webkit < 100) {
      safariVersion = 1;
    } else if (engine.webkit < 312) {
      safariVersion = 1.2;
    } else if (engine.webkit < 412) {
      safariVersion = 1.3;
    } else {
      safariVersion = 2;
    }

    browser.safari = browser.ver = safariVersion;
  }
} else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
  engine.ver = browser.ver = RegExp["$1"];
  engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.gecko = parseFloat(engine.ver);

  // 肯定是不是是Firefox浏览器
  if (/Firefox\/(\S+)/.test(ua)) {
    browser.ver = RegExp["$1"];
    browser.firefox = parseFloat(browser.ver);
  }
} else if (/MSIE ([^;]+)/.test(ua)) {
  engine.ver = RegExp["$1"];
  engine.ie = parseFloat(engine.ver);
}
  • 有了上述代码今后,我们就可以编写以下逻辑
if (client.engine.webkit) {
  if (client.browser.chrome) {
    // 实行针对Chrome的代码
  } else if (client.browser.safari) {
    // 实行针对Safari的代码
  }
} else if (client.engine.gecko) {
  if(client。browser.Firefox) {
    // 实行针对Firefox的代码
  } else {
    // 实行针对其他Gecko浏览器代码
  }
}

辨认平台

  • 浏览器针对差别平台会有差别的版本,如Safari Firefox Opera , 在差别平台下可以会有差别题目。如今三大主流平台是 Window/Mac/Unix(包含种种Linux)。
// 我们的检测代码须要增加平台的检测
var client = function() {
  
  var engine = {

    // 显现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 详细的版本号
    ver: null
  };

  var browser = {

    // 浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,

    // 详细的版本号
    ver: null
  }

  var system = {
    win: false,
    mac: false,
    // Unix
    x11: false
  }

  // 在此检测显现引擎、平台和装备
  ...

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();
  • 检测navigator.platform来肯定平台,在差别浏览器中给出的值都是一致的,检测起来异常直观
var p = navigator.platform;
// window 可以有 Win32 和 Win64
system.win = p.indexOf("Win") == 0;
system.win = p.indexOf("Mac") == 0;
system.win = (p.indexOf("U11") == 0) || (p.indexOf("Linux") == 0);

辨认Windows体系

  • 在WindowsXP之前,Windows有离别针对家庭和贸易用户的两个版本。针对家庭的离别是Windows95和WindowsME。针对贸易的一直叫WindowsNT,末了因为市场缘由改名为Windows2000。这两个产物线厥后又合并成一个由WindowsNT生长而来的大众代码基,代表产物就是WindowsXP。
  • 原著编撰年代较早(2012.3),笔记这里只枚举WindowsXP今后的代码,并补充Windows10
版本IE 4+GeckoOpera < 7Opera 7+WebKit
XP“Windows NT 5.1”“Windows NT 5.1”“WindowsXP”“Windows NT 5.1”“Windows NT 5.1”
Vista“Windows NT 6.0”“Windows NT 6.0”n/a“Windows NT 6.0”“Windows NT 6.0”
7“Windows NT 6.1”“Windows NT 6.1”n/a“Windows NT 6.1”“Windows NT 6.1”
10“Windows NT 10.0”“Windows NT 10.0”n/a“Windows NT 10.0”“Windows NT 10.0”
  • 疏忽掉Opera 7- 的用户和WindowsXP之前的体系,将原著代码修正以下:
if (system.win) {
  // 比方在Windows10的Chrome里,userAgent返回字符串
  // Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
  // 我们要提取两个子字符串 'NT' 和 '10.0'
  // ([^dows]{2}) 示意消除包含'dows'的字符今后的后2位字符 到场捕捉组 $1
  // (\d+\.\d+) 示意 x.x(x) 情势的版本号到场捕捉组 $2
  if (/Windows ([^dows]{2})\s?(\d+\.\d+)?/.test(ua)) {
    if (RegExp["$1"] == "NT") {
      switch (RegExp["$2"]) {
        case "5.1":
          system.win = "XP";
          break;
        case "6.0":
          system.win = "Vista";
          break;
        case "6.1":
          system.win = "7";
          break;
        case "10.0":
          system.win = "10";
          break;
        default:
          system.win = "NT";
          break;
      }
    } else {
      system.win = RegExp["$1"];
    }
  }
}

辨认挪动装备

// 我们在体系变量里增加挪动装备的属性
var client = function() {
  ...

  var system = {
    win: false,
    mac: false,
    // Unix
    x11: false,

    // 挪动装备
    iphone: false,
    ipod: false,
    ipad: false,
    ios: false,
    android: false,
    nokiaN: false,
    winMobile: false
  }

  // 在此检测显现引擎、平台和装备
  ...

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();
  • 一般的检测字符串”iphone”, “ipod”, “ipad”, 就可以离别设置相应属性的值了。
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;
  • 除了晓得iOS装备,最好还能晓得iOS的版本号。在iOS3之前,用户代办字符串只包含”CPU like Mac OS”,厥后iPhone中又改成”CPU iPhone OS 3_0 like Mac OS X”,iPad中又改成”CPU OS 3_2 like Mac OS X”。
  • 搜检体系是不是是 Mac OS、字符串中是不是存在”Mobile”,可以保证不管是什么版本,system.ios中都不会是 0
// 检测iOS版本
if (system.max && ua.indexOf("Mobile") > -1) {
  if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) {
    system.ios = parseFloat(RegExp.$1.repalce("_", "."));
  } else {
    system.ios = 2;   // 不能准确推断只能靠猜
  }
}
  • 检测Android操纵体系也很简单,搜刮字符串”Android”并获得紧跟厥后的版本号
// 检测Android版本
if (/Android (\d+\.\d+)/.test(ua)) {
  system.android = parseFloat(RegExp.$1);
}
  • 远在天堂的诺基亚,略
  • 在天堂门口的Windows Phone , 略

辨认游戏体系

  • 除了挪动装备以外,视频游戏体系中的Web浏览器也最先日趋提高。任天堂Wii和PlayStation3+等等。
  • Wii的浏览器现实上是定制版的Opera,特地为Wii Remote设想的。用户代办字符串:Opera/9.10 (Nintendo Wii;U; ; 1621; en)
  • PlayStation的浏览器是本身开辟的,没有基于前面提到的任何显现引擎。用户代办字符串:Mozilla/5.0 (PLAYSTATION 3; 2.00)
// 我们在体系变量里增加游戏装备的属性
var client = function() {
  ...

  var system = {
    ...

    // 游戏体系
    wii: false,
    ps: false
  }

  // 在此检测显现引擎、平台和装备
  ...

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();
  • 检测前述游戏体系的代码以下
system.wii = ua.indexOf("Wii") > -1;
// ps要疏忽大小写
system.ps = /playstation/i.test(ua);

完全的代码

// 个人做了部份修正 修正时候 2019.5.17
var client = function() {
  
  var engine = {

    // 显现引擎
    ie: 0,
    gecko: 0,
    webkit: 0,
    khtml: 0,
    opera: 0,
    // 详细的版本号
    ver: null
  };

  var browser = {

    // 浏览器
    ie: 0,
    firefox: 0,
    safari: 0,
    konq: 0,
    opera: 0,
    chrome: 0,

    // 详细的版本号
    ver: null
  }

  var system = {
    win: false,
    mac: false,
    // Unix
    x11: false,

    // 挪动装备
    iphone: false,
    ipod: false,
    ipad: false,
    ios: false,
    android: false,
    nokiaN: false,
    winMobile: false,

    // 游戏体系
    wii: false,
    ps: false
  }

  // 在此检测显现引擎、平台和装备
  var ua = navigator.userAgent;

  if (window.opera) {
    engine.ver = browser.ver = window.opera.version();
    engine.opera = browser.opera = parseFloat(engine.ver);
  } else if (/AppleWebKit\/(\S+)/.test(ua)) {
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);

    // 肯定是Chrome照样Safari
    if (/Chrome\/(\S+)/.test(ua)) {
      browser.ver = RegExp["$1"];
      browser.chrome = parseFloat(browser.ver);
    } else if (/Version\/(\S+)/.test(ua)) {
      browser.ver = RegExp["$1"];
      browser.safari = parseFloat(browser.ver);
    } else {
      // 近似的肯定版本号
      var safariVersion = 1;
      if (engine.webkit < 100) {
        safariVersion = 1;
      } else if (engine.webkit < 312) {
        safariVersion = 1.2;
      } else if (engine.webkit < 412) {
        safariVersion = 1.3;
      } else {
        safariVersion = 2;
      }

      browser.safari = browser.ver = safariVersion;
    }
  } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
    engine.ver = browser.ver = RegExp["$1"];
    engine.khtml = browser.konq = parseFloat(engine.ver);
  } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);

    // 肯定是不是是Firefox浏览器
    if (/Firefox\/(\S+)/.test(ua)) {
      browser.ver = RegExp["$1"];
      browser.firefox = parseFloat(browser.ver);
    }
  } else if (/MSIE ([^;]+)/.test(ua)) {
    engine.ver = RegExp["$1"];
    engine.ie = parseFloat(engine.ver);
  }

  // 检测浏览器
  browser.ie = engine.ie;
  browser.opera = engine.opera;

  // 检测平台
  var p = navigator.platform;
  system.win = p.indexOf("Win") == 0;
  system.win = p.indexOf("Mac") == 0;
  system.win = (p.indexOf("U11") == 0) || (p.indexOf("Linux") == 0);

  // 检测Windos操纵体系
  // 消除WindosXP之前的体系
  if (system.win) {
    if (/Windows ([^dows]{2})\s?(\d+\.\d+)?/.test(ua)) {
      if (RegExp["$1"] == "NT") {
        switch (RegExp["$2"]) {
          case "5.1":
            system.win = "XP";
            break;
          case "6.0":
            system.win = "Vista";
            break;
          case "6.1":
            system.win = "7";
            break;
          case "10.0":
            system.win = "10";
            break;
          default:
            system.win = "NT";
            break;
        }
      } else {
        system.win = RegExp["$1"];
      }
    }
  }

  // 挪动装备
  // 剔除了诺基亚和windows phone
  system.iphone = ua.indexOf("iPhone") > -1;
  system.ipod = ua.indexOf("iPod") > -1;
  system.ipad = ua.indexOf("iPad") > -1;

  // 检测iOS版本
  if (system.max && ua.indexOf("Mobile") > -1) {
    if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) {
      system.ios = parseFloat(RegExp.$1.repalce("_", "."));
    } else {
      system.ios = 2;   // 不能准确推断只能靠猜
    }
  }

  // 检测Android版本
  if (/Android (\d+\.\d+)/.test(ua)) {
    system.android = parseFloat(RegExp.$1);
  }

  // 游戏体系
  system.wii = ua.indexOf("Wii") > -1;
  system.ps = /playstation/i.test(ua);

  return {
    engine: engine,
    browser: browser,
    system: system
  };
}();

运用要领

  • 用户代办检测是客户端检测的末了一个选项。只需可以,都应当优先采纳才能检测和怪癖检测。用户代办检测平常适用于以下情况:

    • 不能直接准确的运用才能检测或怪癖检测。比方某些浏览器完成了为未来功用预留的存根函数(stub)。在这类情况下,仅测试相应的函数是不是存在还得不到充足信息。
    • 统一款浏览器在差别平台下具有差别的才能。
    • 为了跟踪剖析等目标须要晓得确实的浏览器。
    原文作者:陈思达
    原文地址: https://segmentfault.com/a/1190000019211060
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞