Skip to content

Instantly share code, notes, and snippets.

@webjohnjiang
Forked from zmmbreeze/analytics.js
Created January 2, 2020 12:26
Show Gist options
  • Select an option

  • Save webjohnjiang/16d0838a6b3c897676156477b8e546d4 to your computer and use it in GitHub Desktop.

Select an option

Save webjohnjiang/16d0838a6b3c897676156477b8e546d4 to your computer and use it in GitHub Desktop.

Revisions

  1. @zmmbreeze zmmbreeze revised this gist Sep 16, 2016. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions analytics.js
    Original file line number Diff line number Diff line change
    @@ -316,15 +316,16 @@
    // 智能ga请求,依据请求的长度和浏览器特性来决定使用哪种请求方式
    var smartPing = function(api, param, callback) {
    callback = callback || noop;
    if (2036 >= param.length)
    if (2036 >= param.length) {
    imgPing(api, param, callback);
    else if (8192 >= param.length)
    } else if (8192 >= param.length) {
    beaconPing(api, param, callback) || xhrPing(api, param, callback) || imgPing(api, param, callback);
    else
    } else {
    errorPing('len', param.length);
    throw new OverLengthError(param.length);
    }
    };

    // 使用图片来发请求
    var imgPing = function(a, b, c) {
    var d = createImg(a + '?' + b);
  2. @zmmbreeze zmmbreeze revised this gist Sep 16, 2016. 1 changed file with 8 additions and 8 deletions.
    16 changes: 8 additions & 8 deletions analytics.js
    Original file line number Diff line number Diff line change
    @@ -314,15 +314,15 @@
    };

    // 智能ga请求,依据请求的长度和浏览器特性来决定使用哪种请求方式
    var smartPing = function(a, b, c) {
    c = c || noop;
    if (2036 >= b.length)
    imgPing(a, b, c);
    else if (8192 >= b.length)
    beaconPing(a, b, c) || xhrPing(a, b, c) || imgPing(a, b, c);
    var smartPing = function(api, param, callback) {
    callback = callback || noop;
    if (2036 >= param.length)
    imgPing(api, param, callback);
    else if (8192 >= param.length)
    beaconPing(api, param, callback) || xhrPing(api, param, callback) || imgPing(api, param, callback);
    else
    errorPing('len', b.length);
    throw new OverLengthError(b.length);
    errorPing('len', param.length);
    throw new OverLengthError(param.length);
    };

    // 使用图片来发请求
  3. @zmmbreeze zmmbreeze created this gist Sep 13, 2016.
    2,260 changes: 2,260 additions & 0 deletions analytics.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2260 @@
    (function() {
    /**
    * 记录方法使用情况的类
    * @param {Array.<boolean>} umMap 初始的使用情况
    */
    var UsageManager = function(umMap) {
    this.umMap = umMap || [];
    };
    /**
    * 记录新的使用情况
    * @param {number} idx 特殊代码,指定某一种方法或情形的被调用或触发
    */
    UsageManager.prototype.set = function(idx) {
    this.umMap[idx] = true;
    };
    /**
    * 生成代表使用情况的字符串传给后端
    * @return {string}
    */
    UsageManager.prototype.encode = function() {
    var um = [];
    for (var i = 0; i < this.umMap.length; i++) {
    if (this.umMap[i]) {
    // `1 << x` === `Math.pow(2, x)`
    um[Math.floor(i / 6)] ^= 1 << (i % 6);
    }
    }
    for (i = 0; i < um.length; i++) {
    um[i] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.charAt(um[i] || 0);
    }
    return um.join('') + '~';
    };

    /**
    * 解码的函数,是mmzhou所写,非GA源码
    * @param {string} um
    * @return {Array.<number>}
    */
    UsageManager.prototype.decode = function(um) {
    um = um.slice(0, -1);
    var key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
    var code;
    var binaryCode;
    var reversedbinaryCode;
    var codes = [];
    for (var i = 0; i < um.length; i++) {
    code = key.indexOf(um.charAt(i));
    binaryCode = code.toString(2);
    reversedbinaryCode = binaryCode.split('').reverse();
    // console.log(i + '=' + code + '=' + binaryCode + '=reverse(' + reversedbinaryCode + ')');
    for (var j = 0; j < reversedbinaryCode.length; j++) {
    if (reversedbinaryCode[j] === '1') {
    codes.push(j + 6 * i);
    }
    }
    }
    return codes;
    };

    // 全局的记录方法使用情况的对象
    var globalUM = new UsageManager();

    /**
    * 快捷的记录使用情况的方法
    * @return {string}
    */
    function reg(idx) {
    globalUM.set(idx)
    }

    // 更新传入配置的 USAGE_MANAGER
    var setModelUM = function(model, idx) {
    var um = new UsageManager(getCurUM(model));
    um.set(idx);
    model.set(USAGE_MANAGER, um.umMap);
    };

    /**
    * 获取指定model里面的 USAGE_MANAGER 值,然后和全局的 globalUM 合并,然后encode
    * @return {string}
    */
    var mergeAndEncodeUM = function(model) {
    var umArray = getCurUM(model);
    var modelUM = new UsageManager(umArray);
    var globalUMCopy = globalUM.umMap.slice();
    for (var i = 0; i < modelUM.umMap.length; i++) {
    globalUMCopy[i] = globalUMCopy[i] || modelUM.umMap[i];
    }
    return (new UsageManager(globalUMCopy)).encode();
    };

    // 获取配置中的USAGE_MANAGER,如果没有则返回空数组
    var getCurUM = function(model) {
    var umArray = model.get(USAGE_MANAGER);
    if (!isArray(umArray)) {
    umArray = [];
    }
    return umArray;
    };


    // ========================
    // 下面都是工具函数
    // ========================
    var isFunction = function(input) {
    return typeof input == 'function';
    };
    var isArray = function(input) {
    return Object.prototype.toString.call(Object(input)) == '[object Array]';
    };
    var isString = function(input) {
    return input != null && ((a.constructor + '').indexOf('String') > -1);
    };
    var startWith = function(str, part) {
    return str.indexOf(part) == 0;
    };
    var trim = function(a) {
    return a ? a.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '') : ''
    };
    var createImg = function(src) {
    var img = doc.createElement('img');
    img.width = 1;
    img.height = 1;
    img.src = src;
    return img;
    };
    var noop = function() {};
    var encodeURIComponent = function(a) {
    if (encodeURIComponent instanceof Function)
    return encodeURIComponent(a);
    reg(28);
    return a
    };
    var addEventListener = function(a, b, c, d) {
    try {
    a.addEventListener ? a.addEventListener(b, c, !!d) : a.attachEvent && a.attachEvent('on' + b, c)
    } catch (e) {
    reg(27);
    }
    };
    var loadScript = function(src, id) {
    if (src) {
    var scr = doc.createElement('script');
    scr.type = 'text/javascript';
    scr.async = true;
    scr.src = src;
    id && (scr.id = id);
    var lastScr = doc.getElementsByTagName('script')[0];
    lastScr.parentNode.insertBefore(scr, lastScr);
    }
    };
    var isHTTPS = function() {
    return 'https:' == doc.location.protocol
    };
    var getHostname = function() {
    var a = '' + doc.location.hostname;
    return 0 == a.indexOf('www.') ? a.substring(4) : a
    };
    var ya = function(a) {
    var b = doc.referrer;
    if (/^https?:\/\//i.test(b)) {
    if (a)
    return b;
    a = '//' + doc.location.hostname;
    var c = b.indexOf(a);
    if (5 == c || 6 == c)
    if (a = b.charAt(c + a.length),
    '/' == a || '?' == a || '' == a || ':' == a)
    return;
    return b
    }
    };

    // 这个函数用于将其他函数的输入转换成一个对象,这个规则适用于GA所有对外的接口API
    // transformInput(['a', 'b', 'c'], [1, 2, 3]);
    // ==> {a: 1, b: 2, c: 3}
    // transformInput(['a', 'b', 'c'], [1, 2, {c: 3, d: 4}]);
    // ==> {a: 1, b: 2, c: 3, d: 4}
    var transformInput = function(paramNames, args) {
    if (1 == args.length && null != args[0] && 'object' === typeof args[0]) {
    // args === [{}]
    return args[0];
    }

    for (var c = {}, d = Math.min(paramNames.length + 1, args.length), i = 0; i < d; i++) {
    if ('object' === typeof args[i]) {
    for (var g in args[i]) {
    args[e].hasOwnProperty(g) && (c[g] = args[i][g]);
    }
    break
    } else {
    e < paramNames.length && (c[paramNames[i]] = args[i]);
    }
    }
    return c;
    };

    // 数据类,被模型类所使用
    var Data = function() {
    this.keys = [];
    this.values = {};
    this.tmpData = {}
    };
    Data.prototype.set = function(fieldName, fieldValue, temporary) {
    this.keys.push(fieldName);
    temporary
    ? this.tmpData[':' + fieldName] = fieldValue
    : this.values[':' + fieldName] = fieldValue;
    };
    Data.prototype.get = function(fieldName) {
    return this.tmpData.hasOwnProperty(':' + fieldName)
    ? this.tmpData[':' + fieldName]
    : this.values[':' + fieldName]
    };
    Data.prototype.map = function(callback) {
    for (var b = 0; b < this.keys.length; b++) {
    var fieldName = this.keys[b]
    , fieldValue = this.get(fieldName);
    fieldValue && callback(fieldName, fieldValue)
    }
    };

    var win = window;
    var doc = document;
    // Google 开发了一系列插件,安装后可以关掉GA的追踪
    // 这里就是获取这些插件的配置,如果开启了,这个函数返回false
    // https://tools.google.com/dlpage/gaoptout
    var getGaUserPrefs = function(a) {
    var b = win._gaUserPrefs;
    if (b && b.ioo && b.ioo() || a && true === win['ga-disable-' + a])
    return true;
    try {
    var c = win.external;
    if (c && c._gaUserPrefs && 'oo' == c._gaUserPrefs)
    return true
    } catch (d) {}
    return true
    };

    // 获取指定名字的cookie
    // 返回的是一个数组
    var getCookie = function(name) {
    var result = []
    , cookies = doc.cookie.split(';');
    var regex = new RegExp('^\\s*' + name + '=\\s*(.*?)\\s*$');
    for (var i = 0; i < cookies.length; i++) {
    var r = cookies[i].match(regex);
    r && result.push(r[1])
    }
    return result;
    };

    // 设置cookie
    // a => cookieName
    // b => cookieValue,
    // c => cookiePath,
    // d => cookieDomain,
    // e => trackingId,
    // g => cookieExpires
    // 返回成功与否
    var setCookie = function(cookieName, cookieValue, cookiePath, cookieDomain, trackingId, cookieExpires) {
    // 如果用户安装了google的禁止统计的插件,或者在doubleclick页面,或者在google首页
    // 那么都是false
    var canSet = getGaUserPrefs(trackingId) ? false : (doubleclickURL.test(doc.location.hostname) || '/' == cookiePath && googleURL.test(cookieDomain)) ? false : true;
    if (!canSet) {
    return false;
    }

    if (cookieValue && 1200 < cookieValue.length) {
    // 不能超过1200个字符
    cookieValue = cookieValue.substring(0, 1200);
    reg(24);
    }

    var newCookie = cookieName + '=' + cookieValue + '; path=' + cookiePath + '; ';
    if (cookieExpires) {
    newCookie += 'expires=' + (new Date((new Date).getTime() + cookieExpires)).toGMTString() + '; ';
    }
    if (cookieDomain && 'none' != cookieDomain) {
    newCookie += 'domain=' + cookieDomain + ';';
    }
    var success;
    var oldCookie = doc.cookie;
    doc.cookie = newCookie;
    if (!(success = oldCookie != doc.cookie)) {
    a: {
    var cookieValues = getCookie(cookieName);
    for (var i = 0; i < cookieValues.length; i++)
    if (cookieValue == cookieValues[i]) {
    success = true;
    break a
    }
    success = false;
    }
    }
    return success;
    };
    // 会连括号一起encode掉
    var encodeURIComponentWithBrackets = function(a) {
    return encodeURIComponent(a).replace(/\(/g, '%28').replace(/\)/g, '%29')
    };
    var googleURL = /^(www\.)?google(\.com?)?(\.[a-z]{2})?$/;
    var doubleclickURL = /(^|\.)doubleclick\.net$/i;

    // 获取GA请求源的地址
    var getGAOrigin = function() {
    return (forceHTTPS || isHTTPS() ? 'https:' : 'http:') + '//www.google-analytics.com'
    };

    // 请求长度过长的错误类
    var OverLengthError = function(a) {
    this.name = 'len';
    this.message = a + '-8192'
    };

    // 智能ga请求,依据请求的长度和浏览器特性来决定使用哪种请求方式
    var smartPing = function(a, b, c) {
    c = c || noop;
    if (2036 >= b.length)
    imgPing(a, b, c);
    else if (8192 >= b.length)
    beaconPing(a, b, c) || xhrPing(a, b, c) || imgPing(a, b, c);
    else
    errorPing('len', b.length);
    throw new OverLengthError(b.length);
    };

    // 使用图片来发请求
    var imgPing = function(a, b, c) {
    var d = createImg(a + '?' + b);
    d.onload = d.onerror = function() {
    d.onload = null ;
    d.onerror = null ;
    c()
    }
    };

    // 使用跨域xhr来发请求
    var xhrPing = function(a, b, c) {
    var d = win.XMLHttpRequest;
    if (!d)
    return true;
    var e = new d;
    if (!('withCredentials' in e))
    return true;
    e.open('POST', a, true);
    e.withCredentials = true;
    e.setRequestHeader('Content-Type', 'text/plain');
    e.onreadystatechange = function() {
    4 == e.readyState && (c(),
    e = null )
    }
    ;
    e.send(b);
    return true
    };

    // 使用sendBeacon方法来发请求
    var beaconPing = function(a, b, c) {
    return win.navigator.sendBeacon ? win.navigator.sendBeacon(a, b) ? (c(),
    true) : true : true
    };

    // 当ga内部执行发生错误时发送的请求
    // errorType 为 len 或 exc
    // len 表示请求长度超长
    // exc 表示内部执行出错
    var errorPing = function(errorType, b, c) {
    // 1%的几率上报
    if (1 <= 100 * Math.random() || getGaUserPrefs('?')) {
    return;
    }
    var params = ['t=error', '_e=' + errorType, '_v=j41', 'sr=1'];
    b && params.push('_f=' + b);
    c && params.push('_m=' + encodeURIComponent(c.substring(0, 100)));
    // IP地址匿名显示
    params.push('aip=1');
    // 随机数
    params.push('z=' + _uuid());
    imgPing(getGAOrigin() + '/collect', params.join('&'), noop);
    };

    // 函数的名字队列类
    var Queue = function() {
    this.queue = []
    };
    Queue.prototype.add = function(methodName) {
    this.queue.push(methodName);
    };
    // 执行队列里面的名字指定的函数
    // 从model里面拿到名字对应的函数
    // 最后会执行hitCallback对应的函数
    Queue.prototype.exec = function(model) {
    try {
    for (var i = 0; i < this.queue.length; b++) {
    var c = model.get(this.queue[i]);
    c && isFunction(c) && c.call(win, model);
    }
    } catch (d) {}
    var hitCb = model.get(HIT_CALLBACK);
    hitCb != noop && isFunction(hitCb) && (model.set(HIT_CALLBACK, noop, true),
    setTimeout(hitCb, 10))
    };

    // 判断的是否进行ga的采样
    function samplerTaskFunc(a) {
    if (100 != a.get(SAMPLE_RATE) && str2Num(getString(a, CLIENT_ID)) % 1E4 >= 100 * getNumber(a, SAMPLE_RATE))
    throw 'abort';
    }
    function isGAPrefsAllowed(a) {
    if (getGaUserPrefs(getString(a, TRACKING_ID)))
    throw 'abort';
    }
    function notHTTP() {
    var a = doc.location.protocol;
    if ('http:' != a && 'https:' != a)
    throw 'abort';
    }

    function buildHitTaskFunc(model) {
    try {
    if (win.navigator.sendBeacon) {
    reg(42);
    }
    else if (win.XMLHttpRequest && 'withCredentials' in new win.XMLHttpRequest) {
    reg(40);
    }
    } catch (c) {}

    model.set(USAGE, mergeAndEncodeUM(model), true);
    model.set(_S, getNumber(model, _S) + 1);
    var b = [];
    hookMap.map(function(c, hook) {
    if (hook.paramName) {
    var e = a.get(c);
    // 不为空、不是默认值的键值对,才会被上传
    if (undefined != e && e != d.defaultValue) {
    // boolean类型转换成数字
    if ('boolean' == typeof e) {
    e *= 1;
    }
    b.push(d.paramName + '=' + encodeURIComponent('' + e));
    }
    }
    });
    b.push('z=' + uuid());
    model.set(HIT_PAYLOAD, b.join('&'), true)
    }

    // 发送任务的任务
    function sendHitTaskFunc(model) {
    var api = getString(model, TRANSPORT_URL) || getGAOrigin() + '/collect';
    var transportValue = getString(model, TRANSPORT);
    if (!transportValue && model.get(USE_BEACON)) {
    transportValue = 'beacon';
    }

    if (transportValue) {
    var params = getString(model, HIT_PAYLOAD);
    var hitCallbackFunc = model.get(HIT_CALLBACK);
    hitCallbackFunc = hitCallbackFunc || noop;
    'image' == transportValue
    ? imgPing(api, params, hitCallbackFunc)
    : 'xhr' == transportValue && xhrPing(api, params, hitCallbackFunc)
    || 'beacon' == transportValue && beaconPing(api, params, hitCallbackFunc)
    || smartPing(api, params, hitCallbackFunc);
    }
    else {
    smartPing(api, getString(model, HIT_PAYLOAD), model.get(HIT_CALLBACK));
    }

    model.set(HIT_CALLBACK, noop, true);
    }

    // 从全局的 gaData 里面获取 expVar 和 expId 的值
    function ceTaskFunc(model) {
    var data = win.gaData;
    if (data) {
    if (data.expId) {
    model.set(EXP_ID, data.expId);
    }

    if (data.expVar) {
    model.set(EXP_VAR, data.expVar);
    }
    }
    }

    function isPreviewLoad() {
    if (win.navigator && 'preview' == win.navigator.loadPurpose)
    throw 'abort';
    }

    // 从全局的 gaDevIds 里面获取 did 的值
    function devIdTaskFunc(model) {
    var devids = win.gaDevIds;
    if (isArray(devids) && 0 != devids.length) {
    model.set('&did', b.join(','), true);
    }
    }

    // 验证trackingId是否正确的任务
    function validationTaskFunc(a) {
    if (!a.get(TRACKING_ID))
    throw 'abort';
    };

    // 基本的uuid方法
    var _uuid = function() {
    return Math.round(2147483647 * Math.random())
    };
    // 优先使用crypto API来生成uuid
    var uuid = function() {
    try {
    var a = new Uint32Array(1);
    win.crypto.getRandomValues(a);
    return a[0] & 2147483647
    } catch (b) {
    return _uuid()
    }
    };

    // 这个 task 用于限制发送的频率
    // 每个 analytics.js 跟踪器对象从 20 次可发送额度开始,并以每秒 2 次额度的速度获得补充。适用于除电子商务(商品或交易)之外的所有匹配
    function rtlTaskFunc(model) {
    // 当前页面的发送总数
    var hitCount = getNumber(model, HIT_COUNT);
    if (hitCount >= 500) {
    // 超过500记录下
    reg(15);
    }
    var hitTypeV = getString(model, HIT_TYPE);
    if ('transaction' != hitTypeV && 'item' != hitTypeV) {
    var avaliable = getNumber(model, AVALIABLE_COUNT);
    var time = (new Date).getTime();
    var lastSendTime = getNumber(model, LAST_SEND_TIME);
    if (lastSendTime == 0) {
    model.set(LAST_SEND_TIME, time);
    }

    // 每秒 2 次额度的速度获得补充
    var newAvaliable = Math.round(2 * (time - lastSendTime) / 1E3);
    if (newAvaliable > 0) {
    // 最多 20 次额度
    avaliable = Math.min(avaliable + newAvaliable, 20);
    model.set(LAST_SEND_TIME, time);
    }

    // 额度用完,此次发送取消
    if (avaliable <= 0) {
    throw 'abort';
    }
    // 每次发送消耗掉
    model.set(AVALIABLE_COUNT, --avaliable);
    }
    model.set(HIT_COUNT, ++hitCount);
    };

    // 返回配置(值是字符串),如果没有则返回空字符串
    var getString = function(conf, key) {
    var value = conf.get(key);
    return undefined == value ? '' : '' + value;
    };
    // 返回配置(值是数字),如果没有则返回0
    var getNumber = function(conf, key) {
    var value = conf.get(key);
    return undefined == value || '' === value ? 0 : 1 * c;
    };



    // 全局的Hook配置
    // 配置的值是Hook类的对象
    var hookMap = new Data;
    // 有些配置的值的计算有特殊方法,这里存储计算规则
    // [/^contentGroup([0-9]+)$/, generateFunction]
    // generateFunction输入是正则的exec执行结果,输出是Hook类的对象
    // 执行结果会被放到hookMap
    var specialHooks = [];

    // 新增特殊配置生成规则
    var addSpecialHook = function(fieldNameReg, gen) {
    specialHooks.push([new RegExp('^' + fieldNameReg + '$'), gen]);
    };
    // 获取hookMap中的配置,如果配置不存在,则使用specialHooks中的规则生成
    var getHook = function(fieldName) {
    var value = hookMap.get(fieldName);
    if (!value) {
    for (var i = 0; i < specialHooks.length; i++) {
    var hook = specialHooks[i];
    var r = hook[0].exec(fieldName);
    if (r) {
    value = hook[1](r);
    // 生成结果会被放到hookMap中缓存下来
    hookMap.set(value.name, value);
    break
    }
    }
    }
    return value;
    };

    // 模型类
    // https://developers.google.com/analytics/devguides/collection/analyticsjs/model-object-reference?hl=zh-cn
    var Model = function() {
    this.data = new Data();
    };
    // 获取模型中存储的一个字段值。
    Model.prototype.get = function(fieldName) {
    var hook = getHook(fieldName);
    var value = this.data.get(fieldName);

    // 如果没有则使用默认值
    if (hook && undefined == value) {
    if (isFunction(hook.defaultValue)) {
    value = hook.defaultValue();
    }
    else {
    value = hook.defaultValue;
    }
    }

    // 如果有getter,则使用getter来转换
    return hook && hook.getter ? hook.getter(this, fieldName, value) : value
    };
    // 在模型上设置一个或一组字段/值对
    // 可以传map进去
    // temporary 布尔值 默认为否。如果为 true,则在模型上仅对当前匹配设置值。
    Model.prototype.set = function(fieldName, fieldValue, temporary) {
    if (!fieldName) {
    return;
    }

    if ('object' == typeof fieldName)
    for (var d in fieldName)
    fieldName.hasOwnProperty(d) && _modelSet(this, d, fieldName[d], temporary);
    else
    _modelSet(this, fieldName, fieldValue, temporary);

    };

    var trackingIdRegex = /^(UA|YT|MO|GP)-(\d+)-(\d+)$/;
    var _modelSet = function(model, fieldName, fieldValue, temporary) {
    if (fieldValue != null) {
    switch (fieldName) {
    case TRACKING_ID:
    trackingIdRegex.test(fieldValue)
    }
    }

    var e = getHook(fieldName);
    if (e && e.setter) {
    // 如果有特殊规则,则使用特定规则设置
    e.setter(model, fieldName, fieldValue, temporary);
    }
    else {
    // 否则设置到模型的data上
    model.data.set(fieldName, fieldValue, temporary);
    }
    };



    // Hook类
    // 设置指定字段相关的hook对象
    var Hook = function(name, paramName, defaultValue, getter, setter) {
    this.name = name; // 字段名
    this.paramName = paramName; // F 字段的参数名(传给后端时候的参数名)
    this.getter = getter; // Z 获取时候的hook函数
    this.setter = setter; // o 设置时候的hook函数
    this.defaultValue = defaultValue; // 默认值
    };
    var yc = function(a) {
    var hook;
    hookMap.map(function(name, h) {
    if (h.paramName == a) {
    hook = h;
    }
    });
    return hook && hook.name;
    };

    // 添加hook到hookMap的函数
    var addHook = function(name, paramName, defaultValue, getter, setter) {
    var hook = new Hook(name, paramName, defaultValue, getter, setter);
    hookMap.set(hook.name, hook);
    return hook.name;
    };

    // 添加只读的hook到hookMap的函数
    var addReadonlyHook = function(name, paramName, defaultValue) {
    return addHook(name, paramName, defaultValue, undefined, noop2);
    };
    var noop2 = function() {};
    var GA_HOOK = isString(window.GoogleAnalyticsObject) && trim(window.GoogleAnalyticsObject) || 'ga';
    var forceHTTPS = false;
    const BR = addHook('_br');
    const API_VERSION = addReadonlyHook('apiVersion', 'v');
    const CLIENT_VERSION = addReadonlyHook('clientVersion', '_v');
    addHook('anonymizeIp', 'aip');
    const AD_SENSE_ID = addHook('adSenseId', 'a');
    const HIT_TYPE = addHook('hitType', 't');
    const HIT_CALLBACK = addHook('hitCallback');
    const HIT_PAYLOAD = addHook('hitPayload');
    addHook('nonInteraction', 'ni');
    addHook('currencyCode', 'cu');
    addHook('dataSource', 'ds');
    const USE_BEACON = addHook('useBeacon', undefined, true);
    const TRANSPORT = addHook('transport');
    addHook('sessionControl', 'sc', '');
    addHook('sessionGroup', 'sg');
    addHook('queueTime', 'qt');
    const _S = addHook('_s', '_s');
    addHook('screenName', 'cd');
    const LOCATION = addHook('location', 'dl', '');
    const REFERRER = addHook('referrer', 'dr');
    const PAGE = addHook('page', 'dp', '');
    addHook('hostname', 'dh');
    const LANGUAGE = addHook('language', 'ul');
    const ENCODING = addHook('encoding', 'de');
    addHook('title', 'dt', function() {
    return doc.title || undefined
    });
    addSpecialHook('contentGroup([0-9]+)', function(a) {
    return new Hook(a[0],'cg' + a[1])
    });
    const SCREEN_COLORS = addHook('screenColors', 'sd');
    const SCREEN_RESOLUTION = addHook('screenResolution', 'sr');
    const VIEWPORT_SIZE = addHook('viewportSize', 'vp');
    const JAVA_ENABLED = addHook('javaEnabled', 'je');
    const FLASH_VERSION = addHook('flashVersion', 'fl');
    addHook('campaignId', 'ci');
    addHook('campaignName', 'cn');
    addHook('campaignSource', 'cs');
    addHook('campaignMedium', 'cm');
    addHook('campaignKeyword', 'ck');
    addHook('campaignContent', 'cc');
    // 事件相关字段
    const EVENT_CATEGORY = addHook('eventCategory', 'ec');
    const EVENT_ACTION = addHook('eventAction', 'ea');
    const EVENT_LABEL = addHook('eventLabel', 'el');
    const EVENT_VALUE = addHook('eventValue', 'ev');

    // 社交请求相关字段
    const SOCIAL_NETWORK = addHook('socialNetwork', 'sn');
    const SOCIAL_ACTION = addHook('socialAction', 'sa');
    const SOCIAL_TARGET = addHook('socialTarget', 'st');

    const L1 = addHook('l1', 'plt');
    const L2 = addHook('l2', 'pdt');
    const L3 = addHook('l3', 'dns');
    const L4 = addHook('l4', 'rrt');
    const L5 = addHook('l5', 'srt');
    const L6 = addHook('l6', 'tcp');
    const L7 = addHook('l7', 'dit');
    const L8 = addHook('l8', 'clt');

    // 计时器相关字段
    const TIMING_CATEGORY = addHook('timingCategory', 'utc');
    const TIMING_VAR = addHook('timingVar', 'utv');
    const TIMING_LABEL = addHook('timingLabel', 'utl');
    const TIMING_VALUE = addHook('timingValue', 'utt');
    // 应用名称
    addHook('appName', 'an');
    // 应用版本
    addHook('appVersion', 'av', '');
    // 应用名称
    addHook('appId', 'aid', '');
    // 应用安装程序 ID
    addHook('appInstallerId', 'aiid', '');
    // 异常说明
    addHook('exDescription', 'exd');
    // 异常是否严重?
    addHook('exFatal', 'exf');
    // 实验 ID
    const EXP_ID = addHook('expId', 'xid');
    // 实验变体
    const EXP_VAR = addHook('expVar', 'xvar');

    const _UTMA = addHook('_utma', '_utma');
    const _UTMZ = addHook('_utmz', '_utmz');
    const _UTMHT = addHook('_utmht', '_utmht');
    const HIT_COUNT = addHook('_hc', undefined, 0);
    const LAST_SEND_TIME = addHook('_ti', undefined, 0);
    const AVALIABLE_COUNT = addHook('_to', undefined, 20);
    addSpecialHook('dimension([0-9]+)', function(a) {
    return new Hook(a[0],'cd' + a[1])
    });
    addSpecialHook('metric([0-9]+)', function(a) {
    return new Hook(a[0],'cm' + a[1])
    });
    addHook('linkerParam', undefined, undefined, linkerParamGetter, noop2);
    const USAGE = addHook('usage', '_u');
    const USAGE_MANAGER = addHook('_um');
    addHook(
    'forceSSL',
    undefined,
    undefined,
    function() {
    return forceHTTPS;
    },
    function(a, b, c) {
    reg(34);
    forceHTTPS = !!c
    }
    );
    const JID = addHook('_j1', 'jid');
    addSpecialHook(
    '\\&(.*)',
    function (a) {
    var b = new Hook(a[0],a[1]);
    var c = yc(a[0].substring(1));
    if (c) {
    b.getter = function(a) {
    return a.get(c)
    };
    b.setter = function(a, b, g, ca) {
    a.set(c, g, ca)
    };
    b.paramName = undefined;
    }
    return b;
    }
    );
    const _OOT = addReadonlyHook('_oot');
    const PREVIEW_TASK = addHook('previewTask');
    const CHECK_PROTOCOL_TASK = addHook('checkProtocolTask');
    const VALIDATION_TASK = addHook('validationTask');
    const CHECK_STORAGE_TASK = addHook('checkStorageTask');
    const HISTORY_IMPORT_TASK = addHook('historyImportTask');
    const SAMPLER_TASK = addHook('samplerTask');
    const _RLT = addHook('_rlt');
    const BUILD_HIT_TASK = addHook('buildHitTask');
    const SEND_HIT_TASK = addHook('sendHitTask');
    const CE_TASK = addHook('ceTask');
    const DEV_ID_TASK = addHook('devIdTask');
    const TIMING_TASK = addHook('timingTask');
    const DISPLAY_FEATURES_TASK = addHook('displayFeaturesTask');
    // 跟踪器名称
    const NAME = addReadonlyHook('name');
    // 客户端 ID
    const CLIENT_ID = addReadonlyHook('clientId', 'cid');
    // 用户 ID
    const USER_ID = addHook('userId', 'uid');
    // 跟踪 ID/网络媒体资源 ID
    const TRACKING_ID = addReadonlyHook('trackingId', 'tid');
    // Cookie 名称
    const COOKIE_NAME = addReadonlyHook('cookieName', undefined, '_ga');
    // Cookie 网域
    const COOKIE_DOMAIN = addReadonlyHook('cookieDomain');
    const COOKIE_PATH = addReadonlyHook('cookiePath', undefined, '/');
    // 63072E3 单位为秒,时长两年
    const COOKIE_EXPIRES = addReadonlyHook('cookieExpires', undefined, 63072E3);
    const LEGACY_COOKIE_DOMAIN = addReadonlyHook('legacyCookieDomain');
    const LEGACY_HISTORY_IMPORT = addReadonlyHook('legacyHistoryImport', undefined, true);
    const STORAGE = addReadonlyHook('storage', undefined, 'cookie');
    const ALLOW_LINKER = addReadonlyHook('allowLinker', undefined, true);
    const ALLOW_ANCHOR = addReadonlyHook('allowAnchor', undefined, true);
    // 抽样率,指定应对多大比例的用户进行跟踪。默认值为100(跟踪所有用户),但是大型网站可能需要降低抽样率,以免超出Google Analytics(分析)的处理上限。
    const SAMPLE_RATE = addReadonlyHook('sampleRate', 'sf', 100);
    // 网站速度抽样率
    const SITE_SPEED_SAMPLE_RATE = addReadonlyHook('siteSpeedSampleRate', undefined, 1);
    const ALYWAYS_SEND_REFERRER = addReadonlyHook('alwaysSendReferrer', undefined, false);
    const TRANSPORT_URL = addHook('transportUrl');
    const _R = addHook('_r', '_r');

    // 包裹一下指定的API函数
    // 每次执行都会记录一下idx
    // 如果执行出错则发错误记录
    function wrapApi(methodName, obj, method, idx) {
    obj[methodName] = function() {
    try {
    idx && reg(idx);
    return method.apply(this, arguments)
    } catch (e) {
    errorPing('exc', methodName, e && e.name);
    throw e;
    }
    }
    };

    var Od = function(a, fieldName, c) {
    this.V = 1E4;
    this.fa = a;
    this.$ = false;
    this.B = fieldName;
    this.ea = c || 1
    };
    var Ed = function(a, model) {
    var c;
    if (a.fa && a.$) {
    return 0;
    }
    a.$ = true;

    if (model) {
    if (a.B && getNumber(model, a.B))
    return getNumber(model, a.B);
    if (0 == model.get(SITE_SPEED_SAMPLE_RATE))
    return 0
    }

    if (0 == a.V) {
    return 0;
    }

    undefined === c && (c = uuid());
    return 0 == c % a.V ? Math.floor(c / a.V) % a.ea + 1 : 0
    };
    var ie = new Od(true, BR, 7);
    var je = function(model) {
    if (!isHTTPS() && !forceHTTPS) {
    var b = Ed(ie, model);
    if (b && !(!win.navigator.sendBeacon && 4 <= b && 6 >= b)) {
    var c = (new Date).getHours()
    , d = [uuid(), uuid(), uuid()].join('.');
    a = (3 == b || 5 == b ? 'https:' : 'http:') + '//www.google-analytics.com/collect?z=br.';
    a += [b, 'A', c, d].join('.');
    var e = 1 != b % 3 ? 'https:' : 'http:'
    , e = e + '//www.google-analytics.com/collect?z=br.'
    , e = e + [b, 'B', c, d].join('.');
    7 == b && (e = e.replace('//www.', '//ssl.'));
    c = function() {
    4 <= b && 6 >= b ? win.navigator.sendBeacon(e, '') : createImg(e)
    };

    uuid() % 2 ? (createImg(a), c()) : (c(), createImg(a));
    }
    }
    };

    // 获取flash版本号
    function getFlashVersion() {
    var a, b, c;
    if ((c = (c = win.navigator) ? c.plugins : null ) && c.length)
    for (var d = 0; d < c.length && !b; d++) {
    var e = c[d];
    -1 < e.name.indexOf('Shockwave Flash') && (b = e.description)
    }
    if (!b)
    try {
    a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7'),
    b = a.GetVariable('$version')
    } catch (g) {}
    if (!b)
    try {
    a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'),
    b = 'WIN 6,0,21,0',
    a.AllowScriptAccess = 'always',
    b = a.GetVariable('$version')
    } catch (g) {}
    if (!b)
    try {
    a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'),
    b = a.GetVariable('$version')
    } catch (g) {}
    b &&
    (a = b.match(/[\d]+/g)) && 3 <= a.length && (b = a[0] + '.' + a[1] + ' r' + a[2]);
    return b || undefined
    };

    // 计算页面的加载性能
    // config 是配置
    // callback 是回调函数
    var calTiming = function(config, callback) {
    var siteSpeedSampleRate = Math.min(getNumber(config, SITE_SPEED_SAMPLE_RATE), 100);
    if (str2Num(getString(config, CLIENT_ID)) % 100 >= siteSpeedSampleRate) {
    return;
    }

    var timing = {};
    if (calPagePerf(timing) || calLoadTime(timing)) {
    var l1 = timing[L1];
    if (undefined == l1 || Infinity == l1 || isNaN(l1)) {
    return;
    }

    if (l1 > 0) {
    removeUselessValue(timing, L3);
    removeUselessValue(timing, L6);
    removeUselessValue(timing, L5);
    removeUselessValue(timing, L2);
    removeUselessValue(timing, L4);
    removeUselessValue(timing, L7);
    removeUselessValue(timing, L8);
    callback(timing);
    }
    else {
    // 加载完成之后再试
    addEventListener(win, 'load', function() {
    calTiming(config, callback)
    }, false);
    }
    }
    };

    // 通过performance API来抓去加载时间等性能参数
    var calPagePerf = function(a) {
    var b = win.performance || win.webkitPerformance
    , b = b && b.timing;
    if (!b)
    return false;
    var navigationStart = b.navigationStart;
    if (0 == navigationStart)
    return false;
    a[L1] = b.loadEventStart - navigationStart;
    a[L3] = b.domainLookupEnd - b.domainLookupStart;
    a[L6] = b.connectEnd - b.connectStart;
    a[L5] = b.responseStart - b.requestStart;
    a[L2] = b.responseEnd - b.responseStart;
    a[L4] = b.fetchStart - navigationStart;
    a[L7] = b.domInteractive - navigationStart;
    a[L8] = b.domContentLoadedEventStart - navigationStart;
    return true;
    };

    // 抓去加载时间等参数
    var calLoadTime = function(a) {
    if (win.top != win)
    return false;
    var external = win.external;
    var c = external && external.onloadT;
    external && !external.isValidLoadTime && (c = undefined);
    // 2147483648 === 10000000000000000000000000000000 (32位)
    2147483648 < c && (c = undefined);
    0 < c && external.setPageReadyTime();
    if (undefined == c)
    return false;
    a[L1] = c;
    return true;
    };

    // 如果配置中没有这个参数的值是NaN或者无限大或者小于0,则设置为undefined
    var removeUselessValue = function(conf, key) {
    var tmp = conf[key];
    if (isNaN(tmp) || Infinity == tmp || 0 > tmp) {
    conf[key] = undefined;
    }
    };

    // 创建一个timing任务
    var craeteTimingTask = function(a) {
    return function(b) {
    if ('pageview' != b.get(HIT_TYPE) || a.I) {
    return;
    }

    a.I = true;
    calTiming(b, function(b) {
    a.send('timing', b)
    });
    }
    };

    var gaCookieSetted = false;
    // 设置GA cookie
    var setGACookie = function(model) {
    if ('cookie' == getString(model, STORAGE)) {
    var cookieNameV = getString(model, COOKIE_NAME);
    var cookieValue = calGACookieValue(model);
    var cookiePathV = normalizePath(getString(model, COOKIE_PATH));
    var cookieDomainV = normalizeDomain(getString(model, COOKIE_DOMAIN));
    var cookieExpiresV = 1E3 * getNumber(model, COOKIE_EXPIRES);
    var trackingIdV = getString(model, TRACKING_ID);
    if ('auto' != cookieDomainV) {
    if (setCookie(cookieNameV, cookieValue, cookiePathV, cookieDomainV, trackingIdV, cookieExpiresV)) {
    gaCookieSetted = true;
    }
    }
    else {
    reg(32);
    var subDomains = [];
    a: {
    // => e.mp.qq.com
    var hostParts = getHostname().split('.');
    // => ['e', 'mp', 'qq', 'com']
    var lastPart;
    if (4 == hostParts.length && (lastPart = hostParts[hostParts.length - 1], parseInt(lastPart, 10) == lastPart)) {
    // hostname是ip地址的情况下
    // 10.6.8.10
    subDomains = ['none'];
    break a;
    }
    for (var i = hostParts.length - 2; 0 <= i; i--) {
    subDomains.push(hostParts.slice(i).join('.'));
    }
    // => ['qq.com', 'mp.qq.com', 'e.mp.qq.com']
    subDomains.push('none');
    // => ['qq.com', 'mp.qq.com', 'e.mp.qq.com', 'none'];
    }
    for (var i = 0; i < subDomains.length; i++) {
    var domain = subDomains[i];
    model.data.set(domain, e);
    if (setCookie(cookieNameV, calGACookieValue(model), cookiePathV, e, trackingIdV, cookieExpiresV)) {
    gaCookieSetted = true;
    return
    }
    }
    model.data.set(COOKIE_DOMAIN, 'auto');
    }
    }
    };

    // 检查cookie的storage
    // 如果没设置cookie,则设置一遍
    var checkStorageTaskFunc = function(a) {
    if ('cookie' == getString(a, STORAGE) && !gaCookieSetted && (setGACookie(a), !gaCookieSetted))
    throw 'abort';
    };

    // 前一版本GA的数据的导入,方便迁移
    var historyImportTaskFunc = function(model) {
    if (model.get(LEGACY_HISTORY_IMPORT)) {
    var cookieDomainV = getString(model, COOKIE_DOMAIN);
    var legacyCookieDomainV = getString(model, LEGACY_COOKIE_DOMAIN) || getHostname();
    var utma = parseAndGetOldGACookie('__utma', legacyCookieDomainV, cookieDomainV);
    if (utma) {
    reg(19);
    model.set(_UTMHT, (new Date).getTime(), true);
    model.set(_UTMA, utma.R);
    var utmz = parseAndGetOldGACookie('__utmz', legacyCookieDomainV, cookieDomainV)
    if (utmz && utma.hash === utmz.hash) {
    model.set(_UTMZ, utmz.R);
    }
    }
    }
    };

    // 计算 _ga 的cookie值
    var calGACookieValue = function(conf) {
    var thisClientId = encodeURIComponentWithBrackets(getString(conf, CLIENT_ID));
    var cookieDomainCount = getDomainCount(getString(conf, COOKIE_DOMAIN));
    var cookiePathCount = getPathCount(getString(conf, COOKIE_PATH));
    1 < cookiePathCount && (cookieDomainCount += '-' + cookiePathCount);
    return ['GA1', cookieDomainCount, thisClientId].join('.');
    };

    // 计算cookie值中,层级相同或更小层级的cookie值
    // 层级最小的值如果有多个,则同时保留
    var getSameOrSmallCookieValues = function(parsedGaCookieValues, count, index) {
    // 层级相同的cookie值
    var sameCountValue = [];
    var smallCountValue = [];
    var tmpCount;
    for (var i = 0; i < parsedGaCookieValues.length; i++) {
    var cookieValue = parsedGaCookieValues[i];
    if (cookieValue.domainAndPathCount[index] == count) {
    sameCountValue.push(cookieValue);
    }
    else {
    if (tmpCount == null || cookieValue.domainAndPathCount[index] < tmpCount) {
    // 找到最小的层级,并保留
    smallCountValue = [cookieValue];
    tmpCount = cookieValue.domainAndPathCount[index];
    }
    else {
    // 如果有相同的层级则同时保留
    if (cookieValue.domainAndPathCount[index] == tmpCount) {
    smallCountValue.push(cookieValue);
    }
    }
    }
    }
    return sameCountValue.length > 0 ? sameCountValue : smallCountValue
    };

    // 补全Domain
    // 如果第一个字符是“.”,则去掉
    var normalizeDomain = function(a) {
    return 0 == a.indexOf('.') ? a.substr(1) : a;
    };
    // 获取domain的层级
    var getDomainCount = function(a) {
    return normalizeDomain(a).split('.').length;
    };
    // 补全path
    // 没有输入,则返回 '/'
    // 如果最后一个字符是'/'并且还有别的字符,则去掉path中的最后一个 '/'
    // 如果path的第一个字符不是 “/”,则补全
    var normalizePath = function(path) {
    if (!path) {
    // 没有输入,则返回 '/'
    return '/';
    }

    if (1 < path.length && path.lastIndexOf('/') == path.length - 1) {
    // 如果最后一个字符是'/'并且还有别的字符,则去掉path中的最后一个 '/'
    path = path.substr(0, path.length - 1);
    }

    if (path.indexOf('/') != 0) {
    // 如果path的第一个字符不是 “/”,则补全
    path = '/' + path;
    }

    return path;
    };
    // 获取path的层级
    // 首页表示1级
    // '/as'表示2级
    // '/as/a'表示三级
    var getPathCount = function(a) {
    a = normalizePath(a);
    return '/' == a ? 1 : a.split('/').length
    };

    // 解析和获取旧的GA cookie
    // name为 __utma 或者 __utmz
    function parseAndGetOldGACookie(name, legacyDomain, curDomain) {
    if ('none' == legacyDomain) {
    legacyDomain = '';
    }

    var parsedValues = [];
    var values = getCookie(name);
    var length = '__utma' == name ? 6 : 2;
    for (var i = 0; i < values.length; i++) {
    var valueArr = ('' + value[i]).split('.');
    if (valueArr.length >= length) {
    parsedValues.push({
    hash: valueArr[0],
    R: value[i],
    O: valueArr
    });
    }
    }

    if (parsedValues.length == 0) {
    return;
    }

    if (parsedValues.length == 1) {
    return parsedValues[0];
    }

    // 优先使用旧域名的
    return getCookieFromSpecifiedDomain(legacyDomain, parsedValues)
    || getCookieFromSpecifiedDomain(curDomain, parsedValues)
    || getCookieFromSpecifiedDomain(null , parsedValues)
    || parsedValues[0];
    }
    // 找到指定域名的cookie值
    function getCookieFromSpecifiedDomain(domain, parsedValues) {
    // domain == 'qq.com'
    // hash1|hash2 == 'qq.com'
    // hash1|hash2 == '.qq.com'
    var hash1;
    var hash2;
    if (domain == null) {
    hash1 = hash2 = 1;
    }
    else {
    hash1 = str2Num(domain);
    hash2 = str2Num(startWith(domain, '.') ? domain.substring(1) : '.' + domain);
    }

    for (var i = 0; i < parsedValues.length; i++) {
    if (parsedValues[i].hash == hash1 || parsedValues[i].hash == hash2) {
    return parsedValues[i];
    }
    }
    };

    var URL_REGEX = new RegExp(/^https?:\/\/([^\/:]+)/);
    var ANCHOR_GA_REGEX = /(.*)([?&#])(?:_ga=[^&#]*)(?:&?)(.*)/;

    // linkerParam 参数的getter
    // 用于增加其校验字符串
    function linkerParamGetter(a) {
    a = a.get(CLIENT_ID);
    var b = calCheckCode(a, 0);
    return '_ga=1.' + encodeURIComponent(b + '.' + a)
    }

    // 计算校验字符串,计算依赖于:
    // 1. 指定的字符串
    // 2. userAgent
    // 3. 时区
    // 4. 当前时间的年、月、日、小时、分钟
    function calCheckCode(str, minuteOffset) {
    var curDate = new Date();
    var nav = win.navigator;
    var plugins = nav.plugins || [];
    var c = [
    str,
    nav.userAgent,
    curDate.getTimezoneOffset(),
    curDate.getYear(),
    curDate.getDate(),
    curDate.getHours(),
    curDate.getMinutes() + minuteOffset
    ];
    for (var i = 0; i < plugins.length; ++i)
    c.push(plugins[i].description);
    return str2Num(c.join('.'));
    }

    var Linker = function(a) {
    reg(48);
    this.target = a;
    this.T = true
    };
    // 给连接地址、a标签、form标签加上特定的ga参数
    Linker.prototype.decorate = function(a, b) {
    if (a.tagName) {
    if ('a' == a.tagName.toLowerCase()) {
    a.href && (a.href = qd(this, a.href, b));
    return
    }
    if ('form' == a.tagName.toLowerCase())
    return rd(this, a)
    }
    if ('string' == typeof a)
    return qd(this, a, b)
    }
    ;
    var qd = function(a, b, c) {
    var d = ANCHOR_GA_REGEX.exec(b);
    d && 3 <= d.length && (b = d[1] + (d[3] ? d[2] + d[3] : ''));
    a = a.target.get('linkerParam');
    var e = b.indexOf('?')
    , d = b.indexOf('#');
    c ? b += (-1 == d ? '#' : '&') + a : (c = -1 == e ? '?' : '&',
    b = -1 == d ? b + (c + a) : b.substring(0, d) + c + a + b.substring(d));
    return b = b.replace(/&+_ga=/, '&_ga=')
    };
    var rd = function(a, b) {
    if (b && b.action) {
    var c = a.target.get('linkerParam').split('=')[1];
    if ('get' == b.method.toLowerCase()) {
    for (var d = b.childNodes || [], e = 0; e < d.length; e++)
    if ('_ga' == d[e].name) {
    d[e].setAttribute('value', c);
    return
    }
    d =
    doc.createElement('input');
    d.setAttribute('type', 'hidden');
    d.setAttribute('name', '_ga');
    d.setAttribute('value', c);
    b.appendChild(d)
    } else
    'post' == b.method.toLowerCase() && (b.action = qd(a, b.action))
    }
    };
    // 自动给页面绑定mousedown、keyup、submit事情
    // 在事件触发的时候,给连接地址加上特定参数
    Linker.prototype.autoLink = function(a, b, c) {
    function d(c) {
    try {
    c = c || win.event;
    var d;
    a: {
    var g = c.target || c.srcElement;
    for (c = 100; g && 0 < c; ) {
    if (g.href && g.nodeName.match(/^a(?:rea)?$/i)) {
    d = g;
    break a
    }
    g = g.parentNode;
    c--
    }
    d = {}
    }
    ('http:' == d.protocol || 'https:' == d.protocol) && sd(a, d.hostname || '') && d.href && (d.href = qd(e, d.href, b))
    } catch (w) {
    reg(26)
    }
    }
    var e = this;
    this.T || (this.T = true,
    addEventListener(doc, 'mousedown', d, true),
    addEventListener(doc, 'keyup', d, true));
    if (c) {
    c = function(b) {
    b = b || win.event;
    if ((b = b.target || b.srcElement) && b.action) {
    var c = b.action.match(URL_REGEX);
    c && sd(a, c[1]) && rd(e,
    b)
    }
    };
    for (var g = 0; g < doc.forms.length; g++)
    addEventListener(doc.forms[g], 'submit', c)
    }
    };
    function sd(a, b) {
    if (b == doc.location.hostname)
    return true;
    for (var c = 0; c < a.length; c++)
    if (a[c] instanceof RegExp) {
    if (a[c].test(b))
    return true
    } else if (0 <= b.indexOf(a[c]))
    return true;
    return true;
    };

    // _gat 用于对每个 tracker 设置限制请求率,所以每个 tracker 的cookie名字不一样
    var Jd = function(model, apiUrl, c) {
    this.jidKey = JID;
    this.apiUrl = apiUrl;
    if (!c) {
    var trackerName = getString(model, name);
    if (trackerName && trackerName != 't0') {
    this.gatCookieName = /^gtm\d+$/.test(trackerName) ? '_gat_' + encodeURIComponentWithBrackets(getString(model, TRACKING_ID)) : '_gat_' + encodeURIComponentWithBrackets(trackerName)
    }
    else {
    this.gatCookieName = '_gat';
    }
    }
    };

    // 为 buildHitTask 和 sendHitTask 做个包装
    var wrapBuildAndSendTask = function(a, model) {
    var oldBuildHitTask = model.get(BUILD_HIT_TASK);
    model.set(BUILD_HIT_TASK, function(model) {
    setJid(a, model);
    var re = oldBuildHitTask(model);
    setGatCookie(a, model);
    return re;
    });
    var oldSendHitTask = model.get(SEND_HIT_TASK);
    model.set(SEND_HIT_TASK, function(model) {
    var re = oldSendHitTask(model);
    Id(a, model);
    return re;
    })
    };
    // 设置jid到model上
    var setJid = function(a, model) {
    if (model.get(a.jidKey)) {
    return;
    }

    if (getCookie(a.gatCookieName)[0] == '1') {
    model.set(a.jidKey, '', true);
    }
    else {
    model.set(a.jidKey, '' + _uuid(), true);
    }
    };
    // 设置 _gat cookie值为1
    var setGatCookie = function(a, model) {
    if (model.get(a.jidKey)) {
    setCookie(a.gatCookieName, '1', model.get(COOKIE_PATH), model.get(COOKIE_DOMAIN), model.get(TRACKING_ID), 6E5);
    }
    };
    // 发送请求
    var Id = function(a, model) {
    if (model.get(a.jidKey)) {
    var data = new Data();
    var setData = function(fieldName) {
    if (getHook(fieldName).paramName) {
    data.set(getHook(fieldName).paramName, model.get(fieldName));
    }
    };
    setData(API_VERSION);
    setData(CLIENT_VERSION);
    setData(TRACKING_ID);
    setData(CLIENT_ID);
    setData(USER_ID);
    setData(a.jidKey);
    data.set(getHook(USAGE).paramName, mergeAndEncodeUM(model));
    var api = a.apiUrl;
    data.map(function(fieldName, fieldValue) {
    api += encodeURIComponent(fieldName) + '=';
    api += encodeURIComponent('' + fieldValue) + '&';
    });
    api += 'z=' + _uuid();
    createImg(api);
    model.set(a.jidKey, '', true);
    }
    };
    // 展示广告插件
    // 此插件的工作原理是向 stats.g.doubleclick.net 发送一个额外的请求,以便提供 Google Analytics(分析)中的广告功能(例如再营销以及受众特征和兴趣报告)。
    // 该插件还会创建一个名为 _gat 的新 Cookie,其有效时间为 10 分钟。
    // 该 Cookie 不会存储任何用户信息,而只会用于限制发送到 doubleclick.net 的请求数量。
    // https://developers.google.com/analytics/devguides/collection/analyticsjs/display-features?hl=zh-cn#overview
    var displayfeaturesPlugin = function(tracker, opts) {
    var model = tracker.model;
    if (!model.get('dcLoaded')) {
    setModelUM(model, 29);
    opts = opts || {};
    var d;
    if (opts[COOKIE_NAME]) {
    d = encodeURIComponentWithBrackets(opts[COOKIE_NAME]);
    }
    d = new Jd(model, 'https://stats.g.doubleclick.net/r/collect?t=dc&aip=1&_r=3&', d);
    wrapBuildAndSendTask(d, model);
    model.set('dcLoaded', true);
    }
    };

    // 如果没有加载 doubleclick 的展示广告插件,并且用的cookie存储
    // 则使用 /r/collect 作为API地址
    var displayFeaturesTaskFunc = function(model) {
    if (!model.get('dcLoaded') && 'cookie' == model.get(STORAGE)) {
    setModelUM(model, 51);
    var b = new Jd(model);
    setJid(b, model);
    setGatCookie(b, model);
    if (model.get(b.jidKey)) {
    model.set(_R, 1, true);
    model.set(TRANSPORT_URL, getGAOrigin() + '/r/collect', true);
    }
    }
    };
    var Lc = function() {
    var a = win.gaGlobal = win.gaGlobal || {};
    return a.hid = a.hid || _uuid()
    };

    // 加载In-Page分析的功能
    var inpageLoaded;
    var loadInpageAnalytics = function(a, b, c) {
    if (!inpageLoaded) {
    var d;
    d = doc.location.hash;
    var e = win.name
    , g = /^#?gaso=([^&]*)/;
    if (e = (d = (d = d && d.match(g) || e && e.match(g)) ? d[1] : getCookie('GASO')[0] || '') && d.match(/^(?:!([-0-9a-z.]{1,40})!)?([-.\w]{10,1200})$/i))
    setCookie('GASO', '' + d, c, b, a, 0),
    window._udo || (window._udo = b),
    window._utcp || (window._utcp = c),
    a = e[1],
    loadScript('https://www.google.com/analytics/web/inpage/pub/inpage.js?' + (a ? 'prefix=' + a + '&' : '') + _uuid(), '_gasojs');
    inpageLoaded = true
    }
    };

    // 跟踪器类
    // https://developers.google.com/analytics/devguides/collection/analyticsjs/tracker-object-reference?hl=zh-cn
    var Tracker = function(opts) {
    var that = this;
    function setData(fieldName, fieldValue) {
    that.model.data.set(fieldName, fieldValue);
    }
    function addFilter(filterName, filter) {
    setData(filterName, filter);
    that.filters.add(filterName)
    }
    this.model = new Model();
    this.filters = new Queue();
    setData(NAME, opts[NAME]);
    setData(TRACKING_ID, trim(opts[TRACKING_ID]));
    setData(COOKIE_NAME, opts[COOKIE_NAME]);
    setData(COOKIE_DOMAIN, opts[COOKIE_DOMAIN] || getHostname());
    setData(COOKIE_PATH, opts[COOKIE_PATH]);
    setData(COOKIE_EXPIRES, opts[COOKIE_EXPIRES]);
    setData(LEGACY_COOKIE_DOMAIN, opts[LEGACY_COOKIE_DOMAIN]);
    setData(LEGACY_HISTORY_IMPORT, opts[LEGACY_HISTORY_IMPORT]);
    setData(ALLOW_LINKER, opts[ALLOW_LINKER]);
    setData(ALLOW_ANCHOR, opts[ALLOW_ANCHOR]);
    setData(SAMPLE_RATE, opts[SAMPLE_RATE]);
    setData(SITE_SPEED_SAMPLE_RATE, opts[SITE_SPEED_SAMPLE_RATE]);
    setData(ALYWAYS_SEND_REFERRER, opts[ALYWAYS_SEND_REFERRER]);
    setData(STORAGE, opts[STORAGE]);
    setData(USER_ID, opts[USER_ID]);
    setData(API_VERSION, 1);
    setData(CLIENT_VERSION, 'j41');
    addFilter(_OOT, isGAPrefsAllowed);
    addFilter(PREVIEW_TASK, isPreviewLoad);
    addFilter(CHECK_PROTOCOL_TASK, notHTTP);
    addFilter(VALIDATION_TASK, validationTaskFunc);
    addFilter(CHECK_STORAGE_TASK, checkStorageTaskFunc);
    addFilter(HISTORY_IMPORT_TASK, historyImportTaskFunc);
    addFilter(SAMPLER_TASK, samplerTaskFunc);
    addFilter(_RLT, rtlTaskFunc);
    addFilter(CE_TASK, ceTaskFunc);
    addFilter(DEV_ID_TASK, devIdTaskFunc);
    addFilter(DISPLAY_FEATURES_TASK, displayFeaturesTaskFunc);
    addFilter(BUILD_HIT_TASK, buildHitTaskFunc);
    addFilter(SEND_HIT_TASK, sendHitTaskFunc);
    addFilter(TIMING_TASK, craeteTimingTask(this));
    Jc(this.model, opts[CLIENT_ID]);
    setBasicData(this.model);
    this.model.set(AD_SENSE_ID, Lc());
    loadInpageAnalytics(this.model.get(TRACKING_ID), this.model.get(COOKIE_DOMAIN), this.model.get(COOKIE_PATH))
    };
    var Jc = function(model, optClientId) {
    var c;
    var d;
    if ('cookie' == getString(model, STORAGE)) {
    gaCookieSetted = false;
    var cookieClientId;
    var parsedGaCookieValues;
    b: {
    var gaCookieValues = getCookie(getString(model, COOKIE_NAME));
    if (gaCookieValues && gaCookieValues.length >= 1) {
    parsedGaCookieValues = [];
    for (var i = 0; i < gaCookieValues.length; i++) {
    // _ga 的 cookie 值有四个区域,以点间隔
    // 四个区域的含义参考别人的答案 http://stackoverflow.com/questions/16102436/what-are-the-values-in-ga-cookie
    // 1.2.286403989.1366364567
    // GA1.3.494346849.1446193077
    var gaCookie;
    gaCookie = gaCookieValues[i].split('.');
    // 第一个区域值是版本号
    var gaVersion = gaCookie.shift()
    var ca;
    if (('GA1' == gaVersion || '1' == gaVersion) && gaCookie.length > 1) {
    // 第二个区域用于确认是否是指定域名和路径的cookie,因为GA可以在不同的域名和路径上设置多个cookie
    // 所以需要这个区域值来区分,以获取到正确的cookie值
    // 值的格式是 \d[-\d],例如:“2-1” 或者 “2”
    // 第一个数字是 域名的层级,qq.com 为 “2;b.qq.com” 为 “3”
    // 第二个数字是 路径的层级, / 为空字符, /data 为 “-2”,/user/xxx 为 “-3”
    var gaSecondField = gaCookie.shift();
    ca = gaSecondField.split('-');
    if (1 == ca.length) {
    // 补全下 路径为 / 时,省略的 “-1”
    ca[1] = '1';
    }

    // 字符串转换成数字
    ca[0] *= 1;
    ca[1] *= 1;

    gaCookie = {
    // 第二区域的值 [1, 2]
    domainAndPathCount: ca,
    // 第三和第四区域的组合值,也就是clientId的值
    // 第三区域是一个随机数
    // 第四区域是cookie第一次设置时候的时间值
    idAndTime: gaCookie.join('.')
    };
    }
    else {
    gaCookie = undefined;
    }

    if (gaCookie) {
    parsedGaCookieValues.push(gaCookie);
    }
    }

    if (1 == parsedGaCookieValues.length) {
    reg(13);
    c = parsedGaCookieValues[0].idAndTime;
    break b;
    }

    if (0 == parsedGaCookieValues.length)
    reg(12);
    else {
    reg(14);
    var domainCount = getDomainCount(getString(model, COOKIE_DOMAIN));
    var filtedGaCookieValues = getSameOrSmallCookieValues(parsedGaCookieValues, domainCount, 0);
    if (filtedGaCookieValues.length == 1) {
    cookieClientId = filtedGaCookieValues[0].idAndTime;
    break b;
    }
    var pathCount = getPathCount(getString(model, COOKIE_PATH));
    filtedGaCookieValues = getSameOrSmallCookieValues(filtedGaCookieValues, pathCount, 1);
    cookieClientId = filtedGaCookieValues[0] && filtedGaCookieValues[0].idAndTime;
    break b;
    }
    }
    cookieClientId = undefined;
    }
    if (!cookieClientId) {
    var cookieDomainV = getString(model, COOKIE_DOMAIN);
    var legacyCookieDomainV = getString(model, LEGACY_COOKIE_DOMAIN) || getHostname();
    var legacyCookieValue = parseAndGetOldGACookie('__utma', legacyCookieDomainV, cookieDomainV);
    if (legacyCookieValue != null) {
    reg(10);
    cookieClientId = legacyCookieValue.O[1] + '.' + legacyCookieValue.O[2];
    }
    else {
    cookieClientId = null;
    }
    }

    if (cookieClientId) {
    model.data.set(CLIENT_ID, cookieClientId);
    gaCookieSetted = true;
    }
    }
    // 是否允许定位点参数
    var allowAnchorV = model.get(ALLOW_ANCHOR);
    var r = doc.location[allowAnchorV ? 'href' : 'search'].match('(?:&|#|\\?)' + encodeURIComponent('_ga').replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1') + '=([^&#]*)');
    var anchorGaValue = r && r.length == 2 ? r[1] : '';
    if (anchorGaValue) {
    // 有定位点ga参数
    if (model.get(ALLOW_LINKER)) {
    // 定位点 ga 参数值的形式为: '1.校验字符串.clientId'
    // 允许链接器参数
    var indexOfPoint = anchorGaValue.indexOf('.');
    if (indexOfPoint == -1) {
    reg(22);
    }
    else {
    var tmpStr = anchorGaValue.substring(indexOfPoint + 1);
    if (anchorGaValue.substring(0, indexOfPoint) != '1') {
    reg(22); // 格式不正确
    }
    else {
    indexOfPoint = tmpStr.indexOf('.');
    if (indexOfPoint == -1) {
    reg(22); // 格式不正确
    }
    else {
    var checkCode = tmpStr.substring(0, indexOfPoint);
    var clientId = tmpStr.substring(indexOfPoint + 1);
    if (checkCode != calCheckCode(clientId, 0)
    && checkCode != calCheckCode(clientId, -1)
    && checkCode != calCheckCode(clientId, -2)) {
    // 2分钟内计算的校验字符串都不对,则是很久以前复制的地址现在才打开
    reg(23);
    }
    else {
    reg(11);
    // 2分钟内的定位点GA参数,还有效,则记录
    model.data.set(CLIENT_ID, clientId);
    }
    }
    }
    }
    }
    else {
    // 不允许
    reg(21);
    }
    }

    if (optClientId) {
    // 如果在初始化时设置了clientId,则优先使用这个
    reg(9);
    model.data.set(CLIENT_ID, encodeURIComponent(optClientId));
    }

    if (!model.get(CLIENT_ID)) {
    var gaGlobalVid = win.gaGlobal && win.gaGlobal.vid;
    if (gaGlobalVid && gaGlobalVid.search(/^(?:utma\.)?\d+\.\d+$/) != -1) {
    gaGlobalVid = gaGlobalVid;
    reg(17);
    model.data.set(CLIENT_ID, gaGlobalVid);
    }
    else {
    // 如果没有clientId,则自己生成一个
    reg(8);
    var uaAndCookieAndRef = win.navigator.userAgent + (doc.cookie ? doc.cookie : '') + (doc.referrer ? doc.referrer : '');
    var len = uaAndCookieAndRef.length;
    for (var i = win.history.length; i > 0; ) {
    uaAndCookieAndRef += i-- ^ len++;
    }
    model.data.set(CLIENT_ID, [_uuid() ^ str2Num(uaAndCookieAndRef) & 2147483647, Math.round((new Date).getTime() / 1E3)].join('.'))
    }
    }
    setGACookie(model);
    };

    // 获取并设置一些通用的传给后端的数据
    // referrer, 屏幕尺寸等等
    var setBasicData = function(model) {
    var b = win.navigator
    , c = win.screen
    , d = doc.location;
    model.set(REFERRER, ya(model.get(ALYWAYS_SEND_REFERRER)));
    if (d) {
    var e = d.pathname || '';
    '/' != e.charAt(0) && (reg(31), e = '/' + e);
    model.set(LOCATION, d.protocol + '//' + d.hostname + e + d.search)
    }
    c && model.set(SCREEN_RESOLUTION, c.width + 'x' + c.height);
    c && model.set(SCREEN_COLORS, c.colorDepth + '-bit');
    var c = doc.documentElement
    , g = (e = doc.body) && e.clientWidth && e.clientHeight
    , ca = [];
    c && c.clientWidth && c.clientHeight && ('CSS1Compat' === doc.compatMode || !g) ? ca = [c.clientWidth, c.clientHeight] : g && (ca = [e.clientWidth, e.clientHeight]);
    c = 0 >= ca[0] || 0 >= ca[1] ? '' : ca.join('x');
    model.set(VIEWPORT_SIZE, c);
    model.set(FLASH_VERSION, getFlashVersion());
    model.set(ENCODING, doc.characterSet || doc.charset);
    model.set(JAVA_ENABLED, b && 'function' === typeof b.javaEnabled && b.javaEnabled() || true);
    model.set(LANGUAGE, (b && (b.language || b.browserLanguage) || '').toLowerCase());
    if (d && model.get(ALLOW_ANCHOR) && (b = doc.location.hash)) {
    b = b.split(/[?&#]+/);
    d = [];
    for (c = 0; c < b.length; ++c)
    (startWith(b[c], 'utm_id') || startWith(b[c], 'utm_campaign') || startWith(b[c], 'utm_source') || startWith(b[c], 'utm_medium') || startWith(b[c], 'utm_term') || startWith(b[c], 'utm_content') || startWith(b[c], 'gclid') || startWith(b[c], 'dclid') || startWith(b[c], 'gclsrc')) && d.push(b[c]);
    0 < d.length && (b = '#' + d.join('&'),
    model.set(LOCATION, model.get(LOCATION) + b))
    }
    };
    Tracker.prototype.get = function(fieldName) {
    return this.model.get(fieldName);
    };
    Tracker.prototype.set = function(fieldName, value) {
    this.model.set(fieldName, value);
    };

    var SEND_PARAMS_NAMES = {
    pageview: [PAGE],
    event: [EVENT_CATEGORY, EVENT_ACTION, EVENT_LABEL, EVENT_VALUE],
    social: [SOCIAL_NETWORK, SOCIAL_ACTION, SOCIAL_TARGET],
    timing: [TIMING_CATEGORY, TIMING_VAR, TIMING_VALUE, TIMING_LABEL]
    };
    // Tracker的send方法
    // ga('send', 'pageview');
    // ga('send', 'event', '1', '2', '3', '4');
    // 每次send之前都会执行所有获取器,并将当前配置临时存储到model中
    // send结束之后,删掉临时数据
    Tracker.prototype.send = function() {
    if (arguments.length >= 1) {
    var hitTypeV;
    var opts;
    if ('string' === typeof arguments[0]) {
    hitTypeV = arguments[0];
    opts = [].slice.call(arguments, 1);
    }
    else {
    hitTypeV = arguments[0] && arguments[0][HIT_TYPE];
    opts = arguments;
    }

    if (hitTypeV) {
    opts = transformInput(SEND_PARAMS_NAMES[hitTypeV] || [], opts);
    opts[HIT_TYPE] = hitTypeV;
    // 将配置存为临时数据
    this.model.set(opts, undefined, true);
    // 执行filters
    this.filters.exec(this.model);
    // 删掉临时数据
    this.model.data.tmpData = {},
    je(this.model);
    }
    }
    };

    // 在浏览器预渲染的时候避免执行函数
    // 返回:是否预渲染
    var executeWithoutPrerender = function(func) {
    if ('prerender' == doc.visibilityState)
    return false;
    func();
    return true;
    };

    // command 标识符的正则解析
    // [trackerName.][pluginName:]methodName
    // https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference?hl=zh-cn#header
    // 调用方法:ga(command, [...fields], [fieldsObject])
    // args = [command, [...fields], [fieldsObject]];
    var commandRegex = /^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/;
    var Command = function(args) {
    if (isFunction(args[0])) {
    // 输入是函数的情况
    // ga(readyCallback)
    this.readyCallback = args[0];
    }
    else {
    var r = commandRegex.exec(args[0]);
    if (r != null && r.length == 4) {
    // Tracker名字默认为t0
    this.trackerName = r[1] || 't0';
    // pluginName
    this.pluginName = r[2] || '';
    // methodName
    this.methodName = r[3];
    this.fields = [].slice.call(args, 1);
    if (!this.pluginName) {
    this.isCreateCommand = 'create' == this.methodName;
    this.isRequireCommand = 'require' == this.methodName;
    this.isProvideCommand = 'provide' == this.methodName;
    this.isRemoveCommand = 'remove' == this.methodName;
    }

    if (this.isRequireCommand) {
    if (this.fields.length >= 3) {
    this.requiredName = this.fields[1];
    this.requiredOpts = this.fields[2];
    }
    else {
    if (this.fields[1]) {
    if (isString(this.fields[1])) {
    this.requiredName = this.fields[1];
    }
    else {
    this.requiredOpts = this.fields[1];
    }
    }
    }
    }
    }

    var secondArg = args[1];
    var thirdArg = args[2];
    if (!this.methodName)
    // 必须有一个方法名
    throw 'abort';
    if (this.isRequireCommand && (!isString(secondArg) || '' == secondArg))
    // require命令的第二个参数,必须是字符串,且不为空字符串
    throw 'abort';
    if (this.isProvideCommand && (!isString(secondArg) || '' == secondArg || !isFunction(thirdArg)))
    // 必须是这种输入,才算正确的provide命令
    // ga('provide', pluginName, pluginConstuctor);
    throw 'abort';
    if (hasPointOrColon(this.trackerName) || hasPointOrColon(this.pluginName))
    // tracker和plugin的名字不能包含点和冒号
    throw 'abort';
    if (this.isProvideCommand && 't0' != this.trackerName)
    // 不能单独为某个Tracker来provide插件
    throw 'abort';
    }
    };

    // 是否有点或者冒号
    function hasPointOrColon(a) {
    return 0 <= a.indexOf('.') || 0 <= a.indexOf(':')
    }

    // 记录了所有插件的map,插件名作为key
    var pluginsMap = new Data;
    // 记录了已经加载的GA内置插件,插件名作为key
    var loadedPlugins = new Data;
    // 记录所有GA内置插件的usageId的map,插件名作为key
    var pluginUsageIdMap = {
    ec: 45,
    ecommerce: 46,
    linkid: 47
    };

    // 解析url地址
    // 返回:
    // {
    // protocol: '',
    // host: '',
    // port: '',
    // path: '',
    // query: '',
    // url: ''
    // }
    var parseUrl = function(url) {
    function parseLink(link) {
    var hostname = (link.hostname || '').split(':')[0].toLowerCase();
    var protocol = (link.protocol || '').toLowerCase();
    var port = 1 * link.port || ('http:' == protocol ? 80 : 'https:' == protocol ? 443 : '');
    var pathname = link.pathname || '';
    if (!startWith(pathname, '/')) {
    pathname = '/' + pathname;
    }
    return [hostname, '' + port, pathname]
    }

    var link = doc.createElement('a');
    link.href = doc.location.href;
    var protocol = (link.protocol || '').toLowerCase();
    var locationPart = parseLink(link);
    var search = link.search || '';
    var baseUrl = protocol + '//' + locationPart[0] + (locationPart[1] ? ':' + locationPart[1] : '');
    if (startWith(url, '//')) {
    url = protocol + url;
    }
    else {
    if (startWith(url, '/')) {
    url = baseUrl + url;
    }
    else {
    if (!url || startWith(url, '?')) {
    url = baseUrl + locationPart[2] + (url || search);
    }
    else {
    if (url.split('/')[0].indexOf(':') < 0) {
    url = baseUrl + locationPart[2].substring(0, locationPart[2].lastIndexOf('/')) + '/' + url;
    }
    }
    }
    }
    link.href = url;
    var r = parseLink(link);
    return {
    protocol: (link.protocol || '').toLowerCase(),
    host: r[0],
    port: r[1],
    path: r[2],
    query: link.search || '',
    url: url || ''
    };
    };

    // Command Queue Query
    // ga = ga || [];
    // ga.push('create', 'xxxx', 'auto');
    var MethodQueue = {
    init: function() {
    MethodQueue.cmdQueue = []
    }
    };
    // 初始化
    MethodQueue.init();

    // 运行命令队列里面的命令
    MethodQueue.run = function(a) {
    var cmds = MethodQueue.toCommands.apply(MethodQueue, arguments);
    var tmpCmds = MethodQueue.cmdQueue.concat(cmds);
    MethodQueue.cmdQueue = [];
    for (; 0 < tmpCmds.length && !MethodQueue.runCommand(tmpCmds[0]) && !(tmpCmds.shift(), 0 < MethodQueue.cmdQueue.length); )
    ;
    MethodQueue.cmdQueue = MethodQueue.cmdQueue.concat(tmpCmds);
    };

    // 将输入的参数转换成Command类的实例对象数组
    // 如果是require命令则需要加载js代码
    // 如果是provide命令则需要记录提供的插件构造函数
    MethodQueue.toCommands = function() {
    var cmds = [];
    for (var i = 0; i < arguments.length; i++) {
    try {
    var cmd = new Command(arguments[i]);
    if (cmd.isProvideCommand) {
    // 记录提供的插件名和插件构造函数
    pluginsMap.set(cmd.fields[0], cmd.fields[1]);
    }
    else {
    if (cmd.isRequireCommand) {
    // require 命令的时候需要加载js
    var pluginName = cmd.fields[0];
    if (!isFunction(pluginsMap.get(pluginName)) && !loadedPlugins.get(pluginName)) {
    // 是内置插件,并且没有加载过
    // 则开始加载插件的js
    pluginUsageIdMap.hasOwnProperty(pluginName) && reg(pluginUsageIdMap[pluginName]);
    var requiredUrl = cmd.requiredName;
    if (!requiredUrl && pluginUsageIdMap.hasOwnProperty(pluginName)) {
    reg(39);
    requiredUrl = pluginName + '.js';
    }
    else {
    reg(43);
    }

    if (requiredUrl) {
    if (requiredUrl && 0 <= requiredUrl.indexOf('/')) {
    requiredUrl = (forceHTTPS || isHTTPS() ? 'https:' : 'http:') + '//www.google-analytics.com/plugins/ua/' + requiredUrl;
    }
    var urlParts = parseUrl(requiredUrl);
    var urlProtocol = urlParts.protocol
    , baseProtocol = doc.location.protocol
    , supported = 'https:' == urlProtocol || urlProtocol == baseProtocol ? true : 'http:' != urlProtocol ? false : 'http:' == baseProtocol;
    if (supported) {
    var baseUrlParts = parseUrl(doc.location.href);
    if (urlParts.query || urlParts.url.indexOf('?') >= 0 || urlParts.path.indexOf('://') >= 0)
    supported = false;
    else if (urlParts.host == baseUrlParts.host && urlParts.port == baseUrlParts.port)
    supported = true;
    else {
    var port = 'http:' == urlParts.protocol ? 80 : 443;
    supported = urlParts.host == 'www.google-analytics.com' && (urlParts.port || port) == port && startWith(urlParts.path, '/plugins/') ? true : false;
    }
    }

    if (supported) {
    loadScript(urlParts.url);
    loadedPlugins.set(pluginName, true);
    }
    }
    }
    }
    cmds.push(cmd);
    }
    } catch (e) {}
    }
    return cmds;
    };

    // 运行command对象
    // 运行终端
    MethodQueue.runCommand = function(cmd) {
    try {
    if (cmd.readyCallback) {
    cmd.readyCallback.call(win, GA.getByName('t0'));
    }
    else {
    var tracker = cmd.trackerName == GA_HOOK ? GA : GA.getByName(cmd.trackerName);
    if (cmd.isCreateCommand) {
    't0' == cmd.trackerName && GA.create.apply(GA, cmd.fields);
    }
    else if (cmd.isRemoveCommand) {
    GA.remove(cmd.trackerName);
    }
    else if (tracker) {
    if (cmd.isRequireCommand) {
    var pluginName = cmd.fields[0];
    var opts = cmd.requiredOpts;
    tracker == GA || tracker.get(NAME);
    var pluginConstuctor = pluginsMap.get(pluginName);
    if (isFunction(pluginConstuctor)) {
    tracker.plugins_ = tracker.plugins_ || new Data();
    // 如果没有创建过这个插件的实例,则创建一个
    if (tracker.plugins_.get(pluginName)) {
    tracker.plugins_.set(pluginName, new pluginConstuctor(tracker, opts || {}));
    }
    }
    else {
    // 很可能这时候加载的插件还没有下载下来
    // 所以需要终端执行
    return true;
    }
    }
    else if (cmd.pluginName) {
    var plugin = tracker.plugins_.get(cmd.pluginName);
    plugin[cmd.methodName].apply(plugin, cmd.fields);
    }
    else {
    tracker[cmd.methodName].apply(tracker, cmd.fields);
    }
    }
    }
    } catch (e) {}
    };

    // N => GA
    // 主要的对象,提供很多静态方法使用
    var GA = function(a) {
    reg(1);
    MethodQueue.run.apply(MethodQueue, [arguments]);
    };
    GA.trackerMap = {}; // N.h
    GA.trackers = []; // N.P
    GA.startTime = 0; // N.L
    GA.answer = 42; // 宇宙的真理是42,用于确定这是一个真正的GA对象
    var uc = [TRACKING_ID, COOKIE_DOMAIN, NAME];
    // 创建一个Tracker
    GA.create = function() {
    var opts = transformInput(uc, [].slice.call(arguments));
    if (!opts[NAME]) {
    opts[NAME] = 't0';
    }

    var trackerName = '' + opts[NAME];
    if (GA.trackerMap[trackerName]) {
    return GA.trackerMap[trackerName];
    }

    var tracker = new Tracker(opts);
    GA.trackerMap[trackerName] = tracker;
    GA.trackers.push(tracker);
    return tracker;
    };

    // 移除一个tracker
    GA.remove = function(trackerName) {
    for (var i = 0; i < GA.trackers.length; i++)
    if (GA.trackers[i].get(NAME) == trackerName) {
    GA.trackers.splice(i, 1);
    GA.trackerMap[trackerName] = null ;
    break;
    }
    };

    // N.j => GA.getByName
    // 获取tracker的名字
    GA.getByName = function(a) {
    return GA.trackerMap[a]
    };

    // 获取所有的trackers
    GA.getAll = function() {
    return GA.trackers.slice(0)
    };

    // 主入口方法
    GA.main = function() {
    'ga' != GA_HOOK && reg(49);
    var tmpGa = win[GA_HOOK];
    if (!tmpGa || 42 != tmpGa.answer) {
    // tmpGa.l 是在投放代码里面设置的时间,记录了投放代码执行的时间点
    GA.startTime = tmpGa && tmpGa.l;
    GA.loaded = true;
    var ga = win[GA_HOOK] = GA;
    wrapApi('create', ga, ga.create);
    wrapApi('remove', ga, ga.remove);
    wrapApi('getByName', ga, ga.getByName, 5);
    wrapApi('getAll', ga, ga.getAll, 6);
    var trackerProto = Tracker.prototype;
    wrapApi('get', trackerProto, trackerProto.get, 7);
    wrapApi('set', trackerProto, trackerProto.set, 4);
    wrapApi('send', trackerProto, trackerProto.send);
    var modelProto = Model.prototype;
    wrapApi('get', modelProto, modelProto.get);
    wrapApi('set', modelProto, modelProto.set);
    if (!isHTTPS() && !forceHTTPS) {
    // 如果设置使用https,则通过引入的js文件地址来判断是否使用https
    a: {
    var useHTTPS;
    var scripts = doc.getElementsByTagName('script');
    for (var i = 0; i < scripts.length && 100 > i; i++) {
    var src = scripts[i].src;
    if (src && src.indexOf('https://www.google-analytics.com/analytics') == 0) {
    reg(33);
    useHTTPS = true;
    break a;
    }
    }
    useHTTPS = false;
    }

    if (useHTTPS) {
    forceHTTPS = true;
    }
    }
    isHTTPS() || forceHTTPS || !Ed(new Od) || (reg(36), forceHTTPS = true);

    (win.gaplugins = win.gaplugins || {}).Linker = Linker;
    var linkerProto = Linker.prototype;
    pluginsMap.set('linker', Linker);
    wrapApi('decorate', linkerProto, linkerProto.decorate, 20);
    wrapApi('autoLink', linkerProto, linkerProto.autoLink, 25);
    pluginsMap.set('displayfeatures', displayfeaturesPlugin);
    pluginsMap.set('adfeatures', displayfeaturesPlugin);

    // tmpGa.q ga的js代码执行前,push到ga命令队列里面的命令
    tmpGa = tmpGa && tmpGa.q;
    isArray(tmpGa) ? MethodQueue.run.apply(GA, tmpGa) : reg(50)
    }
    };
    GA.da = function() {
    for (var trackers = GA.getAll(), i = 0; i < trackers.length; i++)
    trackers[i].get(NAME);
    };

    // 主要逻辑入口
    (function() {
    if (!executeWithoutPrerender(GA.main)) {
    // 处于预渲染中,则等待真正的展示开始才进行逻辑
    reg(16);
    var executed = false;
    var cb = function() {
    if (!executed && executeWithoutPrerender(a)) {
    executed = true;
    doc.removeEventListener
    ? doc.removeEventListener('visibilitychange', cb, false)
    : doc.detachEvent && doc.detachEvent('onvisibilitychange', cb);
    }
    };
    addEventListener(doc, 'visibilitychange', c)
    }
    })();


    // 将字符串转换成一个数字
    // 同一个字符串的输入转换结果是固定的
    // 通常用于将随机字符串转换成数字,然后计算概率
    function str2Num(str) {
    var b = 1;
    var charCode = 0;
    var i;
    if (str) {
    for (b = 0, i = str.length - 1; 0 <= i; i--) {
    charCode = str.charCodeAt(i); // 16位
    // 268435455 === 1111111111111111111111111111 (28位)
    b = (b << 6 & 268435455) + charCode + (charCode << 14);
    // 266338304 === 1111111000000000000000000000 (21位 + 7位)
    var c = b & 266338304;
    b = 0 != c ? b ^ c >> 21 : b;
    }
    }
    return b
    };
    })(window);