简体   繁体   English

为什么Segment.io loader脚本将方法名称/ args推送到看似被覆盖的队列?

[英]Why does the Segment.io loader script push method names/args onto a queue which seemingly gets overwritten?

I've been dissecting the following code snippet, which is used to asynchronously load the Segment.io analytics wrapper script: 我一直在剖析以下代码片段,它用于异步加载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');

It's well commented and I can see what it's doing, but I'm puzzled when it comes to the methodFactory function, which pushes details (method name and arguments) of any method calls made before the main analytics.js script has loaded onto the global analytics array. 它评论很好,我可以看到它正在做什么,但是当涉及到methodFactory函数时,我感到困惑,因为它推动了在将主要analytics.js脚本加载到全局之前进行的任何方法调用的细节(方法名称和参数)。 analytics数组。

This is all well and good, but then if/when the main script does load, it seemingly just overwrites the global analytics variable (see last line here ), so all that data will be lost. 这是一切都很好,但随后如果/当主脚本执行负载,它似乎只是覆盖全球analytics变量(见这里最后一行 ),因此所有数据都将丢失。

I see how this prevents script errors in a web page by stubbing out methods which don't exist yet, but I don't understand why the stubs can't just return an empty function: 我看到这是如何通过截断尚不存在的方法来防止网页中的脚本错误,但我不明白为什么存根不能只返回一个空函数:

var methods = ['identify', 'track', 'trackLink', 'trackForm', 'trackClick',
               'trackSubmit', 'pageview', 'ab', 'alias', 'ready'];

for (var i = 0; i < methods.length; i++) {
    lib[methods[i]] = function () { };
}

What am I missing? 我错过了什么? Please, help me understand! 拜托,帮帮我理解!

Ian here, co-founder at Segment.io—I didn't actually write that code, Calvin did, but I can fill you in on what it's doing. Ian在这里,Segment.io的联合创始人 - 我实际上并没有写那个代码,Calvin做了,但是我可以告诉你它正在做什么。

You're right, the methodFactory is stubbing out the methods so that they are available before the script loads, which means people can call analytics.track without wrapping those calls in an if or ready() call. 你是对的, methodFactory正在删除这些方法,以便在脚本加载之前它们可用,这意味着人们可以调用analytics.track而不将这些调用包装在ifready()调用中。

But the methods are actually better than "dumb" stubs, in that they save the method that was called, so we can replay the actions later. 但是这些方法实际上比“哑”存根更好,因为它们保存了被调用的方法,因此我们可以稍后重放这些操作。 That's this part: 这就是这个部分:

analytics.push([type].concat(Array.prototype.slice.call(arguments, 0)));

To make that more readable: 为了使其更具可读性:

var methodFactory = function (method) {
    return function () {
        var args = Array.prototype.slice.call(arguments, 0);
        var newArgs = [method].concat(args);
        analytics.push(newArgs);
    };
};

It tacks on the name of the method that was called, which means if I analytics.identify('userId') , our queue actually gets an array that looks like: 它取决于所调用方法的名称,这意味着如果我使用analytics.identify('userId') ,我们的队列实际上会得到一个类似于以下内容的数组:

['identify', 'userId']

Then, when our library loads in, it unloads all of the queued calls and replays them into the real methods (that are now available) so that all of the data recorded before load is still preserved. 然后,当我们的库加载时,它卸载所有排队的调用并将它们重放到真实的方法(现在可用)中,以便仍然保留在加载之前记录的所有数据。 That's the key part, because we don't want to just throw away any calls that happen before our library has the chance to load. 这是关键部分,因为我们不想丢弃在我们的库有机会加载之前发生的任何调用。 That looks like this: 看起来像这样:

// 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 is a local variable at that point, and after we're done replaying, we replace the global with the local analytics (which is the real deal). analytics是一个局部变量,在我们完成重放之后,我们将全局替换为本地analytics (这是真正的交易)。

Hope that makes sense. 希望有道理。 We're actually going to have a series on our blog about all the little tricks for 3rd-party Javascript, so you might dig that soon! 我们实际上会在我们的博客上有关于第三方Javascript的所有小技巧的系列文章,所以你很快就会挖掘它!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM