4

编辑

在@Alex 的帮助下学到的教训是,你永远不应该将函数声明放在块范围内。不是我打算这样做,但如果你滑倒了,它可能会导致大问题。


我有一个似乎通过 Google Closure 错误地压缩的脚本文件。当我使用原始代码运行我的应用程序时,一切正常。但是当我尝试使用 Google Closure 压缩它时,会引入一些错误。

我没有使用高级选项;我正在使用基本的默认模式

显然我不能指望任何人调试压缩文件,但我希望有人可以查看未压缩的代码并让我知道我是否以某种方式做了一些疯狂愚蠢的事情会欺骗 Closure。

关于缩小代码的一些注释:

闭包是内联 BEFramework.prototype.hstreamLoad的and BEFramework.prototype.hstreamEvalJson,并且似乎完全删除了辅助函数getDeleteValue,getValueToDisplaygetDisplayForLabel可能的其他函数。

未压缩文件如下。

此代码可以通过此处的关闭手动编译,应该会重现上述症状。

(function() {
    var $ = jQuery;

    // Load and display the messages ("healthstream") for a given module.
    // This requires that the module's HTML have specific features, see
    // dashboard.htm and contactsManager/details/default.htm for examples.
    // This also requires that the `request` support `pageIndex` and `pageSize`,
    // so we can handle paging.
    //
    // Args:    `options`       An options object with these keys:
    //                              `channelId`     The channel ID of the module (for transmitRequest)
    //                              `translationId` Optional alternate ID for translation (if not given,
    //                                              `channelId` is used).
    //                              `action`        The action (for transmitRequest)
    //                                                  - Must support `pageIndex` and `pageSize`
    //                              `request`       The request (for transmitRequest)
    //                                                  - Must include `pageIndex` and `pageSize`
    //                              `complete`      Optional callback triggered when the load is complete.
    //                              `showOptions`   Optional callback if an options menu is supported
    //                                              by the calling module. Receives a raw event instance
    //                                              and the item on which the options were triggered:
    //                                                  function showOptions(event, item)
    //                              `context`       Optional context (`this` value) for the call to
    //                                              `complete` and/or `showOptions`
    BEFramework.prototype.hstreamLoad = hstreamLoad;
    function hstreamLoad(options) {
        var inst = this;

        var channelId, translationId, action, request, complete, showOptions, context,
            pageIndex, pageCount, pageSize, pageCount,
            btnPrevious, btnNext,
            dataShownFlags;

        // Get our arguments (with defaults)
        channelId = options.channelId;
        translationId = options.translationId || options.channelId;
        action = options.action;
        request = $.extend({}, options.request);    // Create a *copy*, because we modify it when doing paging
        complete = options.complete;
        if (typeof complete !== "function") {
            complete = undefined;
        }
        showOptions = options.showOptions;
        if (typeof showOptions !== "function") {
            showOptions = undefined;
        }
        context = options.context;  // (undefined will automatically become the global object)

        // Grab the initial pageIndex and pageSize
        pageIndex = request.pageIndex || 1;
        pageSize = request.pageSize || 100;

        // Disable the button and show "searching" label
        $('#healthStreamSearchButton')
            .button("disable")
            .button("option", "label", BETranslate(translationId, 'HealthStreamSearching'));

        // Hook up the buttons; be a bit paranoid that they've been hooked before and clear previous handlers
        btnPrevious = $('#healthStreamPagePrevious');
        btnNext = $('#healthStreamPageNext');
        btnPrevious.hide().unbind("click.paging").bind("click.paging", goToPreviousPage);
        btnNext.hide().unbind("click.paging").bind("click.paging", goToNextPage);

        // Do it
        doLoad();

        // === Support functions

        // Trigger a load request
        function doLoad() {
            request.pageIndex = pageIndex;
            request.pageSize = pageSize;
            inst._transport.transmitRequest(channelId, action, request, hstreamLoaded);
        }

        // Hndle the load response
        function hstreamLoaded(objResponse) {
            var healthStream = objResponse.items;
            var total = objResponse.total;
            var tbody = $('#healthStreamList');

            // Need to make this update optional
            $('#pageHeaderName').html(BETranslate(translationId, 'HeaderActivity') + ' (' + String(total) + ')');
            $('#healthStreamSearchButton')
                .button("enable")
                .button("option", "label", BETranslate(translationId, 'HealthStreamSearch'));
            tbody.empty();
            btnPrevious.hide();
            btnNext.hide();

            if (healthStream.length > 0) {
                pageCount = Math.ceil(total / pageSize);

                if (pageCount > 1) {
                    if (pageIndex > 1) {
                        btnPrevious.show();
                    }
                    if (pageIndex < pageCount) {
                        btnNext.show();
                    }
                }

                var item;
                var tr;
                var tdMain;
                var daysHash = {};
                var creationDate;
                var key;
                var today = new Date();
                var yesterday = new Date();
                var msg;
                yesterday.setDate(yesterday.getDate() - 1);

                dataShownFlags = {};

                for (var x = 0; x < healthStream.length; x++) {
                    item = healthStream[x];
                    msg = inst.hstreamEvalJson(item);

                    if (msg.length > 0) {
                        creationDate = new Date(item.CreationDate);
                        key = [creationDate.getYear(), creationDate.getMonth(), creationDate.getDate()].join('-');

                        if (!daysHash[key]) {
                            if (isDateEqual(creationDate, today)) {
                                addRowHeader(tbody, BETranslate(inst._channelId, 'HSToday'));
                            }
                            else if (isDateEqual(creationDate, yesterday)) {
                                addRowHeader(tbody, BETranslate(inst._channelId, 'HSYesterday'));
                            }
                            else {
                                addRowHeader(tbody, creationDate.toString('MM/dd/yyyy'));
                            }
                            daysHash[key] = true;
                        }

                        tr = $(
                            "<tr>" +
                                "<td class='date' style='white-space:nowrap;'>" + new Date(item.CreationDate).toString('h:mm tt') + "</td>" +
                                "<td class='main'><span class='name'>" + msg + "</span>" +
                                "</tr>"
                        );
                        tbody.append(tr);
                        if (showOptions) {
                            tr.find("td.main").prepend($("<em rel='opt'>&nbsp;</em>").click(makeShowOptionsHandler(item)));
                        }
                    }
                }

                // If any of the templates created links with a `data` attribute, hook them up
                $('#healthStreamList a[data]').click(showTitle).each(function (index) {
                    this.id = 'data' + index;
                });

            }
            else {
                tbody.html('<tr><td colspan="2">' + BETranslate(inst._channelId, 'HSNoActivity') + '</td></tr>');
            }

            // Trigger completion callback
            if (complete) {
                complete.call(context, objResponse);
            }
        }

        function makeShowOptionsHandler(item) {
            // Our event comes to us from jQuery, but we pass on the raw
            // event to the callback
            return function (event) {
                showOptions.call(context, event.originalEvent || event, item);
            };
        }

        function addRowHeader(listRef, name) {
            listRef.append(
                "<tr>" +
                    "<td colspan='2' class='divider'>" + name + "</td>" +
                    "</tr>"
            );
        }

        function showTitle(event) {

            $.stopEvent(event);

            var link = this;
            var $link = $(this);
            var href = $link.attr("href");  // We want the attribute, not the property (the property is usually expanded)
            var hrefTitle = $link.attr('hreftitle') || BETranslate(inst._channelId, 'HSMoreInfo');
            var data = $link.attr('data') || "";
            var linkId = link.id;

            if (!dataShownFlags[linkId]) {
                dataShownFlags[linkId] = true;
                if (data) {
                    var div = $(
                        "<div class='data'>" +
                            "<span data-linkId='" + linkId + "' class='close'>x</span>" +
                            "<table><thead></thead></table>" +
                            "</div>"
                    );
                    $link.parent().append(div);

                    var thead = div.find("thead");
                    var arr = data.split('~');
                    var splitEntry;

                    for (var x = 0; x < arr.length; x++) {
                        splitEntry = arr[x].split('|');
                        if (splitEntry[0] === 'Changed length') {
                            splitEntry[1] = splitEntry[1].replace(/\d+/g, BEFramework.prettyTime);
                        }
                        if (splitEntry.length > 1 && splitEntry[1].length > 0) {
                            thead.append(
                                "<tr>" +
                                    "<td class='hslabel'>" + splitEntry[0] + ":</td>" +
                                    "<td>" + splitEntry[1] + "</td>" +
                                    "</tr>"
                            );
                        }
                    }

                    div.find("span:first").click(hideTitle);

                    if (href && href !== "#") {
                        $("<a target='_blank'>" + hrefTitle + "</a>").attr("href", href).appendTo(div);
                    }
                }
            }
        }

        function hideTitle(event) {
            var $this = $(this),
                linkId = $this.attr("data-linkId");
            delete dataShownFlags[linkId];
            $this.parent().remove();
            return false;
        }

        function goToPreviousPage(event) {
            --pageIndex;
            doLoad();
            return false;
        }
        function goToNextPage(event) {
            ++pageIndex;
            doLoad();
            return false;
        }
    }

    var ___x = false;
    var __i = 0;

    BEFramework.prototype.hstreamEvalJson = hstreamEvalJson;
    function hstreamEvalJson(item) {
        var inst = this;

        if (item.Action === 'saveinsurance' && !___x && __i != 0){
            var start = +new Date();
            __i = 1;
        }

        var userId = inst._BEUser ? inst._BEUser.getId() : -1;
        var json = eval('(' + item.JSON + ')');
        var key = 'HS' + item.Module + '_' + item.Action;
        var msg = BETranslate(inst._channelId, key);
        var fromIsMe = item.CreatedByContactId == userId;
        var toIsMe = item.ContactId == userId;
        var fromString = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou') + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>';
        var toString = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour') + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>';
        var fromString2 = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>';
        var toString2 = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>';
        var subFormat, subProps;
        var configObject = (BEFramework.healthStreamConfig[item.Module] && BEFramework.healthStreamConfig[item.Module][item.Action]) || {};
        var standardCase = configObject.standardCase;
        var suppress = configObject.suppress || [];
        var propertiesInOrder = configObject.displayOrder || [];

        if (msg.indexOf('not found in module') != -1) {
            try {
                switch (item.Module) {
                    case 'contacts':
                        if (item.Action == 'setpermission' || item.Action == 'deleterelationship' || item.Action == 'addinvite') {
                            msg = BETranslate(inst._channelId, key + json.type.toString());
                        }
                        break;
                    case 'tasks':
                        if (item.Action == 'savetask') {
                            msg = BETranslate(inst._channelId, key + json.type.toString());
                        }
                        break;
                    default:
                        msg = '';
                }
            } catch (ex) {
                msg = '';
            }
        }

        for (var prop in json) {
            if (typeof (json[prop]) == 'object') {

                if (prop === 'changes' || prop === 'deleted'){
                    subProps = json[prop];

                    for (var propName in subProps) {
                        if (indexInArrayCI(propName, propertiesInOrder) === -1 && indexInArrayCI(propName, suppress) === -1){
                            propertiesInOrder.push(propName);
                        }
                    }
                }

                if (prop == 'changes') {
                    var changes = '';
                    var changeFrom = BETranslate(inst._channelId, 'HSChangedFrom');
                    var changeTo = BETranslate(inst._channelId, 'HSChangedTo');

                    for (var i = 0; i < propertiesInOrder.length; i++) {
                        var subprop = propertiesInOrder[i];
                        if (getObjectValCI(subProps, subprop) == null) continue;

                        var subSplit = stripHtml(getObjectValCI(subProps, subprop)).split('|');

                        if (subSplit.length === 1) {
                            subFormat = BETranslate(inst._channelId, 'HS' + item.Module + '_changes_' + subprop);
                            if (subFormat.indexOf('not found in module') < 0) {
                                changes += $.sandr(subFormat, '#{value}', subSplit[0]);
                            }
                            else {
                                changes += "*|" + subprop + " " + subSplit[0] + "~";
                            }
                        }
                        else {
                            var fromValue = stripHtml(subSplit[0]);
                            var toValue = stripHtml(subSplit[1]);

                            var packetInfo = processChangedValues(subprop, fromValue, toValue);
                            if (packetInfo.skip) continue;

                            changes = changes + changeFrom + packetInfo.display + '|' + packetInfo.fromValue + '<b>' + changeTo + '</b>' + packetInfo.toValue + '~';
                        }
                    }

                    msg = $.sandr(msg, '#{' + prop + '}', changes);
                } else if (prop == 'deleted') {
                    var deleted = '';

                    for (var i = 0; i < propertiesInOrder.length; i++) {
                        var subprop = propertiesInOrder[i];
                        var currentValue = getObjectValCI(subProps, subprop);

                        if (currentValue == null || currentValue.toString().length === 0) continue;

                        deleted = deleted + getDisplayForLabel(subprop) + '|' + getDeleteValue(subprop, currentValue) + '~';
                    }

                    msg = $.sandr(msg, '#{' + prop + '}', deleted);
                }
            } else {
                msg = $.sandr(msg, '#{' + prop + '}', $.sandr(json[prop], '"', ' '));
            }

            function processChangedValues(label, fromValue, toValue){
                var typeFormat = (getObjectValCI(configObject, label) || {}).type;
                var result = {};

                if (typeFormat === 'date'){
                    var d1 = new Date(fromValue);
                    var d2 = new Date(toValue);

                    if (isDateEqual(d1, d2)) result.skip = true;
                }

                result.fromValue = getValueToDisplay(fromValue, typeFormat);
                result.toValue = getValueToDisplay(toValue, typeFormat);

                result.display = getDisplayForLabel(label)

                return result;
            }

            function getDeleteValue(label, value){
                var typeFormat = (getObjectValCI(configObject, label) || {}).type;

                return getValueToDisplay(value, typeFormat);
            }

            function getValueToDisplay(rawValue, typeFormat){
                if (typeFormat === 'date'){
                    var d = new Date(rawValue);
                    return isNaN(d.getTime()) ? rawValue : d.toString('MM/dd/yyyy');
                } else if (typeof typeFormat === 'function') {
                    return typeFormat(rawValue)
                } else {
                    return rawValue;
                }
            }

            function getDisplayForLabel(label){
                var fixCaseOfProperty = standardCase === '*' || indexInArrayCI(label, standardCase) > -1;
                var rawConfigForLabel = getObjectValCI(configObject, label) || {};

                return (rawConfigForLabel && rawConfigForLabel.display)
                        || (fixCaseOfProperty ? fixCase(label) : null)
                        || label;
            }
        }

        msg = $.sandr(msg, '#{contactId}', item.ContactId);
        msg = $.sandr(msg, '#{from}', fromString);
        msg = $.sandr(msg, '#{to}', toString);
        msg = $.sandr(msg, '#{from2}', fromString2);
        msg = $.sandr(msg, '#{to2}', toString2);
        msg = $.sandr(msg, '#{recordId}', item.RecordId);

        msg = msg.replace(/#{[\S]*}/g, '');

        if (item.Action === 'saveinsurance' && !___x && __i == 1){
            var end = +new Date();
            ___x = true;
            //alert(end - start);
        }

        if (item.Action === 'saveinsurance') __i++;

        if (msg.indexOf('not found in module') == -1) {
            return msg;
        } else {
            return '';
        }
    }

    function stripHtml(html) {
        var tmp = document.createElement('DIV');
        tmp.innerHTML = html;
        return tmp.textContent || tmp.innerText;
    }

    function isDateEqual(date1, date2) {
        if (date1.getDate() === date2.getDate() &&
            date1.getMonth() === date2.getMonth() &&
            date1.getYear() === date2.getYear()) {
            return true;
        }
        else {
            return false;
        }
    }

    function getObjectValCI(obj, key){
        for (var k in obj){
            if (k.toLowerCase() === key.toLowerCase()){
                return obj[k];
            }
        }
    }

    function indexInArrayCI(item, arr){
        if (!$.isArray(arr)) arr = [];

        var target = item.toString().toLowerCase();

        for (var i = 0; i < arr.length; i++){
            if (target === arr[i].toLowerCase()) return i;
        }
        return -1;
    }

    function fixCase(str){
        return str.replace(/[a-z][A-Z]/g, function(match) { return match.charAt(0) + ' ' + match.charAt(1).toLowerCase(); }).toLowerCase()
            .replace(/\sid\s/g, ' ID ')
            .replace(/\sid$/g,  ' ID')
            .replace(/^id$/g,  'ID');
    }
})();
4

2 回答 2

2

当你使用闭包编译器时,你放弃了对代码的一些控制。它将执行各种技巧,并可能删除未使用的代码。

看起来好像您的函数没有被删除,而是被重命名了。

例如,您致电getDeleteValue...

getDeleteValue(subprop, currentValue)

就是现在...

l(g,r)

因为getDeleteValue没有导出,所以 Closure 重命名了它。

使用 Closure Compiler 需要一些技巧和大量的文档搜索,直到您熟悉它的工作原理。

于 2012-10-31T17:48:05.547 回答
1

好吧,有太多的错误要考虑。首先,我不明白你是想要静态引用还是实例化值。您没有使用 jsDoc 标签或类似的东西。Compiler 仅使用相应的 jsDoc 标签才能做到最好。你的逻辑很奇怪,而且表述不当。原型交替等都发生在 IIFE(立即调用的函数表达式)中。你的函数是静态的吗?他们是构造函数吗?我们是人还是舞者?

IIFE 在浏览器触发 DOMContentLoaded 事件之前执行。您最多可以做一个 jQuery IIFE 等效项$(function() {})();,它将它绑定到 DOMReady 或 DOMContentLoaded 回调。您在块内定义内联函数,这甚至不在 ECMA 语言中。

虽然大多数脚本引擎支持块内的函数声明,但它不是 ECMAScript 的一部分(参见ECMA-262,第 13 和 14 条)。更糟糕的实现彼此不一致,也与未来的 EcmaScript 提案不一致。ECMAScript 只允许在脚本或函数的根语句列表中使用函数声明。而是使用使用函数表达式初始化的变量来定义块内的函数。

var myFunctionName = function (params) {};

您还缺少大量分号。在解释你的 JS 时自动插入分号并不是完美无缺的,所以要养成它的习惯。

依赖隐式插入可能会导致微妙的、难以调试的问题。不要这样做。你比那更好。

有几个地方缺少分号特别危险:

// 1.
MyClass.prototype.myMethod = function() {
  return 42;
}  // No semicolon here.

(function() {
  // Some initialization code wrapped in a function to create a scope for locals.
})();


var x = {
  'i': 1,
  'j': 2
}  // No semicolon here.

// 2.  Trying to do one thing on Internet Explorer and another on Firefox.
// I know you'd never write code like this, but throw me a bone.
[normalVersion, ffVersion][isFF]();


var THINGS_TO_EAT = [apples, oysters, sprayOnCheese]  // No semicolon here.

// 3. conditional execution a la bash
-1 == resultOfOperation() || die();

那么会发生什么?

JavaScript 错误 - 首先调用返回 42 的函数,并将第二个函数作为参数,然后“调用”数字 42 导致错误。当它试图调用x[ffVersion][isIE](). die 被调用 unless resultOfOperation()isNaN并被THINGS_TO_EAT分配die(). 为什么?

JavaScript 要求语句以分号结尾,除非它认为可以安全地推断出它们的存在。在这些示例中的每一个中,都在语句中使用了函数声明或对象或数组字面量。右括号不足以表示语句的结束。如果下一个标记是中缀或括号运算符,则 Javascript 永远不会结束语句。

这确实让人们感到惊讶,因此请确保您的作业以分号结尾。

于 2012-10-31T17:58:56.557 回答