Google Analytics.js代码有什么作用?

时间:2018-10-06 09:35:45

标签: google-analytics

能不能解释一下“ www.google-analytics.com/analytics.js”的作用?我是说代码的工作方式以及2469行代码的作用是什么...

我已经看到了加载此脚本的代码的解释。在这里:Google Analytics Code Explanation。这很有用,但仅说明了最终脚本的加载方式,而不是实际analytics.js文件的内部工作方式。

1 个答案:

答案 0 :(得分:0)

第二部分

第178-194行

      da = function(a) {
        var b = document.createElement("a");
        a && (ye.test(a), b.href = a);
        a = b.pathname;
        "/" !== a[0] && (a = "/" + a);
        var c = b.hostname.replace(Ce, "");
        return {
          href: b.href,
          protocol: b.protocol,
          host: b.host,
          hostname: c,
          pathname: a,
          search: b.search,
          hash: b.hash,
          port: b.port
        }
      },

这使用了一些浏览器技巧。浏览器必须进行一些处理才能创建<a>标记,并且可以利用它。第179行是实际创建<a>元素的位置;它不会出现在页面上,因为它从未被添加为document的后代。第180行只是if (a) b.href = a;ye.test是完全多余的。

现在,这还没有得到很好的记录,但是通过JavaScript可以访问<a>标签的一些特殊属性,例如anchor.pathname。 Internet Explorer提供了类似的属性,只是它不是以"/"开头。第182行检查b.pathname的第一个字符是否为"/",如果不是,则向其添加"/"。 (这纯粹是Internet Explorer的兼容性问题。)第183行使用Beta-{Ce}从主机名中删除端口号。

Beta-da返回一个对象,其中包含href,未规范化的protocolhosthostname-无端口号,{{1} },pathname(查询字符串),search(锚定)和传递给它的网址的hash

我刚刚意识到我已经解码了Beta-port,因此我们可以返回到我在第1部分末尾留下的悬崖峭壁!

返回Beta-ha

Beta-xe始终返回ha。这意味着...是的,我很难相信任何Google员工都会这是愚蠢的,这意味着 我在分析中犯了另一个错误。 (如果需要,则为问题here。)但让我们继续吧。

-1

什么意思

          case "path":
            if (a.pathname.substr(0, 1) == "/") {
              a = a.pathname;
            } else {
              a = "/" + a.pathname;
            }
            a = a.split("/");
            if (0 <= -1) {
              a[a.length - 1] = "";
            }
            a = a.join("/");
            return a;

这意味着...

          case "path":
            if (a.pathname.substr(0, 1) == "/") {
              a = a.pathname;
            } else {
              a = "/" + a.pathname;
            }
            a = a.split("/");
            if (false) {
              a[a.length - 1] = "";
            }
            a = a.join("/");
            return a;

因此,它拆分了 case "path": if (a.pathname.substr(0, 1) == "/") { a = a.pathname; } else { a = "/" + a.pathname; } a = a.split("/"); a = a.join("/"); return a; ,然后又无缘无故地再次加入它。所以我们也可以删除它...

a

它类似于Beta-{ case "path": if (a.pathname.substr(0, 1) == "/") { a = a.pathname; } else { a = "/" + a.pathname; } return a; 中的181和182行。因此,给定da的Beta-xe将输出"path"。这似乎很明智!

第195-212行

pathname

我敢打赌这是另一个哈希函数。 Beta- te, Ae, ve = function(c) { te = te || b(); Ae = Ae || a(); for (var d = [], e = 0; e < c.length; e += 3) { var g = e + 1 < c.length, ca = e + 2 < c.length, Ee = c.charCodeAt(e), l = g ? c.charCodeAt(e + 1) : 0, w = ca ? c.charCodeAt(e + 2) : 0, k = Ee >> 2; Ee = (Ee & 3) << 4 | l >> 4; l = (l & 15) << 2 | w >> 6; w &= 63; ca || (w = 64, g || (l = 64)); d.push(te[k], te[Ee], te[l], te[w]) } return d.join("") }, 是一个带有单个字符串参数的函数。 Beta-{ve和Beta-te是全局变量,它们分别缓存Beta-{Ae和Beta-b()的结果,除非在其他地方被覆盖(我不这样做) 认为就是这种情况)…

我们以前没见过a()吗?

  

现在我们可以看到Beta-te从类似a的对象Array构造一个对象,其中键是值,而值是键,但仅用于数字键。

因此,Beta-te创建了a的一种缓存版本,存储为te.indexOf。由于:

  

Beta-Object返回字符串b

我们知道Beta-{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."将始终包含相同的对象。现在,我开始认为该函数是生成那些随机字符串的函数。

因此,无论如何,Ae循环会在3秒内遍历字符串。如果字符串的部分小于3,则使用for;否则,它将使用角色的代码。然后,它通过移位来执行一些hashy类型的加扰操作,使用Beta-{0将其映射到一个字符,然后将它们全部连接起来。

我将澄清代码:

te

如果我正确阅读的话,这是Base 64的实现。看看Beta-{ te, Ae, ve = function(c) { te = te || b(); Ae = Ae || a(); var chars = []; for (var e = 0; e < c.length; e += 3) { var is_two_long = e + 1 < c.length; var is_three_long = e + 2 < c.length; var Ee = c.charCodeAt(e); var l = is_two_long ? c.charCodeAt(e + 1) : 0; var w = is_three_long ? c.charCodeAt(e + 2) : 0; // the following comments assume that Ee, l and w are 8-bit, // since that's the only way the code makes any sense. // since charCodeAt can return values up to 65535, it's clear // that this code was only tested on English text. // the final values produced are all 6-bit. var k = Ee >> 2; // top 6 bits of Ee Ee = (Ee & 3) << 4 | l >> 4; // bottom 2 bits of Ee, then // top 4 bits of l l = (l & 15) << 2 | w >> 6; // bottom 4 bits of l, then // top 2 bits of w w &= 63; // bottom 6 bits of w // replace nonsense with padding character if (!is_three_long) { w = 64; if (!is_two_long) { l = 64; } } chars.push(te[k], te[Ee], te[l], te[w]); } return chars.join(""); }, ,很明显填充字符(Beta-te)是te[64]而不是.

因此,Beta-{=ve的一个版本,它从一些无效的字符串中创建垃圾,而不是始终引发异常。

哦,谷歌。

第213-236行

c => btoa(c).replace('+', '-').replace('/', '_').replace('=', '.')

我现在称呼它为:Beta- Ke = function(c) { function d(a) { for (; g < c.length;) { var b = c.charAt(g++), d = Ae[b]; if (null != d) return d; if (!/^[\s\xa0]*$/.test(b)) throw Error( "Unknown base64 encoding at char: " + b); } return a } te = te || b(); Ae = Ae || a(); for (var e = "", g = 0;;) { var ca = d(-1), Ee = d(0), l = d(64), w = d(64); if (64 === w && -1 === ca) return e; e += String.fromCharCode(ca << 2 | Ee >> 4); 64 != l && (e += String.fromCharCode(Ee << 4 & 240 | l >> 2), 64 != w && (e += String.fromCharCode(l << 6 & 192 | w))) } }, 从早期开始解码专有的Base 64编码。

Beta-{Ke-Ke接受一个参数,可能是d,如果到达Beta-Number-Ke的末尾,它将返回该参数。 。 Beta-c-Ke是计数器,用于说明我们当前使用Beta-g-Ke的哪个字符。 c循环实际上是变相的for循环。正则表达式可确保在任何字符既无效也不为空白时引发while

Beta-{Error-Ke返回下一个有效的Base 64字符的d,除非找到无效的非空白字符(在这种情况下,它会引发{{ 1}})或字符串用完,在这种情况下,它将返回其单个参数。

在Beta-{Number-Error之后,您将获得“设置Beta-{Ke和Beta-d(如果尚未设置)”,但是因为这与Beta-te中的代码没有什么不同,所以我们不需要对此予以过多关注。

随后的Ae循环仅将Base64转换回“原始”字符串(或考虑到原始字符串可能进行的整形,使其尽可能接近原始字符串)。

这样,Beta-{vefor 完全等效,除了由于常量Ke之外,您甚至不能说它更高效。 Beta-c=>atob(c.replace(/[\s\xa0]*/, "").replace('-', '+').replace('_', '/').replace('.', '='))的作者显然不同于Beta-e += blah

第237-246行

Ke

我尚不知道Beta-ve用于什么用途,但至少我们知道它的范围。

Beta- Le, Me = function() { var a = {}, b = Ie.sa; Ie.sa = void 0 === b ? a : b; a = Ie.sa; (b = a.wa) && b.va || (b = { va: [] }, a.wa = b); return b }, 的含义稍微容易一些,因为:

  

Beta-{LeMe,Beta-Iewindow

这意味着我可以这样重写它:

He

完美!辉煌!惊人! Beta-document会将 Me = function() { if (window.sa === undefined) { window.sa = {}; } var sa = window.sa; if (!sa.wa || !sa.wa.va) { sa.wa = {va: []}; } return sa.wa; }, 初始化为Me,如果未定义window.sa.wa.va,则将[]清除(尽管这可能只是实现细节)。现在我们知道wa的结构了!

第247行

wa.va

Beta-window.sa将尽可能少地匹配,其次是星号,然后尽可能少地匹配,然后是星号,再接着是尽可能多的匹配。因此, Nd = /(.*?)\*(.*?)\*(.*)/, 将根据匹配方法而导致组Nd"abc*def*ghi*jkl"。我不确定这将用于什么。

第248行

["abc*def*ghi*jkl", "abc", "def", "ghi*jkl"]

测试版{["*def*ghi*jkl", "", "def", "ghi*jkl"]将匹配 we = /([^?#]+)(\?[^#]*)?(#.*)?/, we以外的一个或多个字符,可以选择后面跟着?然后是一些非#字符, /或?后跟一些东西。 #将分为#"https://stackoverflow.com/search?q=programming&where=boat#anchor? yaar!""https://stackoverflow.com/search"

这是多余的;代码中已经有其他方法可以做到这一点。但是-这是在Beta-"?q=programming&where=boat"中使用的!

返回Beta-"#anchor? yaar!"

e

我们知道e绝对是URL,因此 function e(a, b, c) { if (c === undefined) { c = false; } var match = we.exec(b); if (!match) return ""; var match1 = match[1]; var g = match[2] || ""; var Ee = match[3] || ""; var Fe = "_gl=" + a; function e(a) { a = d(a); var b = a.charAt(a.length - 1); if (a && b !== "&") { a += "&"; } return a + Fe } if (c) { Ee = "#" + e(Ee.substring(1)); } else { g = "?" + e(g.substring(1)); } return "" + match1 + g + Ee } 成为b似乎毫无意义–为什么在地球上您将GET参数附加到锚点上?

我只能重申:

  

看起来越来越像Beta-c为GET请求建立URL。

第249行

true

哦……这很棘手,但是我想我已经解决了。此正则表达式查找字符串中最早出现e的字符串。仅向其传递不带 ze = /(.*?)(^|&)_gl=([^&]*)&?(.*)/, 的查询字符串才有意义。共有四个匹配组。

  1. 匹配“之前”的所有内容(非贪婪;尽可能少地匹配)。
  2. 如果第一组匹配任何内容,则匹配_gl=;否则为空字符串。
  3. 匹配?参数的“值”。
  4. 匹配&之后的所有内容(如果存在)。

然后,将第1组,第2组和第4组连接起来,得到一个等效字符串,没有_gl。等等...那很熟悉。

返回Beta-&

_gl
  

Beta-d是一个带有 function d(a) { var b = ze.exec(a); if (b) { var c = b[2], d = b[4]; a = b[1]; d && (a = a + c + d) } return a } 参数的函数。如果d String与该字符串匹配,则如果第4组为空,则函数返回第1组,如果不是,则返回第1、2和4组的串联。如果不匹配,该函数将返回它最初传递的字符串。

我可以更新它。

  

Beta-RegExp是一个带有ze参数的函数,代表一个查询字符串。如果存在(最早的)d GET参数,它将删除。

现在我们对Beta-String有了更多的了解。

(再次)返回Beta-_gl

e

考虑到所有这些因素,很明显Beta-e取一个 function e(a, b, c) { if (c === undefined) { c = false; } var match = we.exec(b); if (!match) return ""; var match1 = match[1]; var g = match[2] || ""; var Ee = match[3] || ""; var Fe = "_gl=" + a; function e(a) { a = d(a); var b = a.charAt(a.length - 1); if (a && b !== "&") { a += "&"; } return a + Fe } if (c) { Ee = "#" + e(Ee.substring(1)); } else { g = "?" + e(g.substring(1)); } return "" + match1 + g + Ee } 值,一个URL和一个e,并替换了_gl的值。 URL(如果存在)以第一个参数提供。如果Boolean标志是_gl或未提供,则该函数将具有修改查询字符串的明智行为;如果是Boolean,它将修改定位字符串。

第250-260行

false

美容师在这里做得不好。

true

Beta- Qe = function(a) { var b = [], c; for (c in a) if (a.hasOwnProperty(c)) { var d = a[c]; void 0 !== d && d === d && null !== d && "[object Object]" !== d .toString() && (b.push(c), b.push(ve(String(d)))) } a = b.join("*"); return ["1", Ne(a), a].join("*") }, Qe = function(a) { var b = []; for (var k in a) if (a.hasOwnProperty(k)) { var v = a[k]; if (v !== undefined && v === v && // NaN check v !== null && v.toString() !== "[object Object]") { b.push(k); b.push(ve(String(v))); } } a = b.join("*"); return ["1", Ne(a), a].join("*"); }, 作为参数,并迭代其属性,构建密钥的Qe和Beta-Object的结果(a Base64变体)以Array的形式调用字符串化值。然后将此数组与ve作为分隔符(分配给[key0, ve(String(value0)), key1, ve(String(value1)), …])进行连接,并返回字符串"*"

有趣的是,使用了两种不同的值化方式:a"1*" + Ne(a) + "*" + aAccording to this Stack Overflow question, .toString对于某些未定义String方法但作为String.toString的值的略微更具弹性。等等已经被过滤掉了-null已经被调用而没有任何undefined子句 –我认为这种区别没有任何好处。

目前尚不清楚它的目的是什么,但希望了解.toString会对此有所澄清。哦,多么方便。

第261-281行

catch

啊……那是很多任意的常数和移位。它还使用Beta-{Ne,解决了那个之谜。

      Ne = function(a, b) {
        a = [window.navigator.userAgent, (new Date)
          .getTimezoneOffset(), window.navigator.xa || window.navigator
          .language, Math.floor((new Date)
            .getTime() / 60 / 1E3) - (void 0 === b ? 0 : b), a
        ].join("*");
        if (!(b = Le)) {
          b = Array(256);
          for (var c = 0; 256 > c; c++) {
            for (var d = c, e = 0; 8 > e; e++) d = d & 1 ? d >>> 1 ^
              3988292384 : d >>> 1;
            b[c] = d
          }
        }
        Le = b;
        b = 4294967295;
        for (c = 0; c < a.length; c++) b = b >>> 8 ^ Le[(b ^ a.charCodeAt(
          c)) & 255];
        return ((b ^ -1) >>> 0)
          .toString(36)
      },

Beta-Le是一个接受两个参数的函数,并返回各种数据散列的Base36表示形式。第一个是 Ne = function(a, b) { a = [ window.navigator.userAgent, new Date().getTimezoneOffset(), window.navigator.xa || window.navigator.language, Math.floor(new Date().getTime() / 60 / 1000) - (b === undefined ? 0 : b), a ].join("*"); if (!Le) { Le = Array(256); for (var c = 0; 256 > c; c++) { for (var d = c, e = 0; 8 > e; e++) { d = d & 1 ? d >>> 1 ^ 3988292384 : d >>> 1; } Le[c] = d; } } b = 4294967295; for (c = 0; c < a.length; c++) b = (b >>> 8) ^ Le[(b ^ a.charCodeAt(c)) & 255]; return ((b ^ -1) >>> 0).toString(36); }, ,第二个是(默认为Ne)。

String位实际上是用于初始化Beta-0的代码。我不确定为什么将其放置在中间,而不是通常将初始化代码放置在 的开始,而且我也不知道为什么Beta-if (!Le)被声明得太多考虑到Beta-Le是唯一使用Beta-Le的东西,因此早于Beta-{Ne}。这只是这些谜团中的另一个。

Beta-Ne(如果您想知道的话)总是相同的:

Le
Le
var Le = Array(256);
for (var c = 0; 256 > c; c++) {
  for (var d = c, e = 0; 8 > e; e++) {
    d = d & 1 ? d >>> 1 ^ 3988292384 : d >>> 1;
  }
  Le[c] = d;
}

// See, Google? You're not the only ones who can do this.
var ol = document.querySelector("ol");
Le.forEach((i,a)=>ol.appendChild(((a=document.createElement("li")).innerText=("0000000"+(i>>>0).toString(16)).substr(-8),a)));

因此,这是一种哈希,除了提供的字符串外,还考虑了用户的语言和时区,粗略时间和用户代理。 (在不知道每件事情的可能性小于2²²的情况下),我不认为可以将其取反来提取原始数据。几乎肯定不是很简单。

我不熟悉大多数哈希函数,所以我提出有关堆栈溢出的问题。但是,仔细检查后,表生成中使用的随机十进制常数实际上等效于ol { font-family: monospace; } –我们钟爱的CRC-32常数。因此,这似乎是CRC的简化版本,它仅检查每个值的低字节。

叹气。

激动等待着!确保选择下一期“ Google Analytics.js代码的作用是什么?”出来的时候。