我一直在分析以下代码片段,该代码片段用于异步加载Segment.io分析包装脚本:
// Create a queue, but don't obliterate an existing one!
var analytics = analytics || [];
// Define a method that will asynchronously load analytics.js from our CDN.
analytics.load = function(apiKey) {
// Create an async script element for analytics.js.
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = ('https:' === document.location.protocol ? 'https://' : 'http://') +
'd2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/' + apiKey + '/analytics.min.js';
// Find the first script element on the page and insert our script next to it.
var firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript);
// Define a factory that generates wrapper methods to push arrays of
// arguments onto our `analytics` queue, where the first element of the arrays
// is always the name of the analytics.js method itself (eg. `track`).
var methodFactory = function (type) {
return function () {
analytics.push([type].concat(Array.prototype.slice.call(arguments, 0)));
};
};
// Loop through analytics.js' methods and generate a wrapper method for each.
var methods = ['identify', 'track', 'trackLink', 'trackForm', 'trackClick',
'trackSubmit', 'pageview', 'ab', 'alias', 'ready'];
for (var i = 0; i < methods.length; i++) {
analytics[methods[i]] = methodFactory(methods[i]);
}
};
// Load analytics.js with your API key, which will automatically load all of the
// analytics integrations you've turned on for your account. Boosh!
analytics.load('MYAPIKEY');
它得到了很好的评论,我可以看到它在做什么,但当谈到methodFactory
函数时,我感到困惑,它推送在主analytics.js
脚本加载到全局analytics
数组之前进行的任何方法调用的详细信息(方法名称和参数)。
这一切都很好,但如果/当主脚本加载时,它似乎只是覆盖全局analytics
变量(请参见此处的最后一行),那么所有数据都将丢失。
我看到了这是如何通过清除还不存在的方法来防止网页中的脚本错误的,但我不明白为什么存根不能只返回一个空函数:
var methods = ['identify', 'track', 'trackLink', 'trackForm', 'trackClick',
'trackSubmit', 'pageview', 'ab', 'alias', 'ready'];
for (var i = 0; i < methods.length; i++) {
lib[methods[i]] = function () { };
}
我错过了什么?请帮我理解!
Ian,Segment.io-I的联合创始人,我实际上并没有写那个代码,Calvin写了,但我可以告诉你它在做什么。
你说得对,methodFactory
正在截断这些方法,以便它们在脚本加载之前可用,这意味着人们可以调用analytics.track
,而无需将这些调用封装在if
或ready()
调用中。
但这些方法实际上比"哑"存根更好,因为它们保存了被调用的方法,这样我们以后就可以回放操作了。这部分:
analytics.push([type].concat(Array.prototype.slice.call(arguments, 0)));
为了使其可读性更强:
var methodFactory = function (method) {
return function () {
var args = Array.prototype.slice.call(arguments, 0);
var newArgs = [method].concat(args);
analytics.push(newArgs);
};
};
它附加在被调用的方法的名称上,这意味着如果我analytics.identify('userId')
,我们的队列实际上会得到一个数组,看起来像:
['identify', 'userId']
然后,当我们的库加载时,它会卸载所有排队的调用,并将它们重放到真正的方法(现在可用)中,以便在加载之前记录的所有数据仍然保留。这是关键部分,因为我们不想在我们的库有机会加载之前丢弃任何调用。看起来是这样的:
// Loop through the interim analytics queue and reapply the calls to their
// proper analytics.js method.
while (window.analytics.length > 0) {
var item = window.analytics.shift();
var method = item.shift();
if (analytics[method]) analytics[method].apply(analytics, item);
}
analytics
在这一点上是一个局部变量,在我们完成回放后,我们用局部analytics
替换全局(这是真正的处理)。
希望这是有道理的。实际上,我们将在我们的博客上发布一系列关于第三方Javascript的小技巧,所以你可能很快就会发现!
与问题关系不大,但可能对那些在谷歌上搜索问题的人有用"段不发送排队的事件";。
在我的代码中,我在页面加载阶段将window.analytics
分配给了另一个变量:
let CLIENT = analytics;
然后我使用了这个变量,而不是使用全局analytics
:
CLIENT.track();
CLIENT.page();
// etc
但我遇到了一个问题,有时事件被发送,有时什么也没发送。";有时";页面重新加载之间有所不同。有时,它还可以忽略在页面加载时触发的所有事件,并且在不重新加载页面的情况下,开始发送在页面加载后绑定的事件。
然后我进行了调试,发现CLIENT
在队列中保存了所有未发送的事件。显然,它们是使用CCD_ 15放置的。然后我发现了这个SO问题。所以我认为这就是正在发生的事情:
CLIENT
保存对存根analytics
对象的引用,该对象调用此methodFactory()
。在Segment完全加载后,它用实际代码替换window.analytics
,而CLIENT
仍然保留对旧window.analytics
的引用。这就是为什么这个";有时";有时window.analytics
在加载初始化该CLIENT
的主脚本之前被Segment替换,有时主脚本在Segment脚本之前加载。
新代码:
let CLIENT = undefined;
if (CLIENT) {
CLIENT.page();
} else {
window.analytics.page();
}
我需要这个CLIENT
,因为我在网络和移动设备上使用相同的分析代码。在移动设备上,此CLIENT
将单独初始化,而在网络上,window.analytics
始终可用。