4

編集

@Alex の助けを借りて学んだ教訓は、関数宣言をブロック スコープに入れるべきではないということです。意図したわけではありませんが、間違うと大変な事になりかねません。


Google Closure を介して誤って圧縮されていると思われるスクリプト ファイルがあります。元のコードでアプリを実行すると、すべて正常に動作します。しかし、Google Closure で圧縮しようとすると、いくつかのエラーが発生します。

私は高度なオプションを使用していません。基本的なデフォルトモードを使用しています

もちろん、誰かが圧縮ファイルをデバッグすることは期待できませんが、誰かが圧縮されていないコードを見て、Closure をだますような愚かなことをしていないか教えてくれることを願っています。

縮小されたコードに関する注意事項:

閉鎖はインライン化 BEFramework.prototype.hstreamLoadされており、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 タグなどを使用していません。コンパイラは、対応する jsDoc タグでのみ最適に機能します。あなたの論理は非常に奇妙で、定式化されていません。プロトタイプの変更など、すべて IIFE (即時に呼び出される関数式) で発生します。あなたの関数は静的ですか?彼らはコンストラクターですか?私たち人間かダンサーか?

IIFE は、ブラウザーによって DOMContentLoaded イベントが発生する前に実行されます。できることのほとんどは、$(function() {})();それを DOMReady または DOMContentLoaded コールバックにバインドする jQuery IIFE の同等物です。ブロック内でインライン関数を定義していますが、これは 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 を返す関数が 2 番目の関数をパラメーターとして呼び出され、次に数値 42 が "呼び出され" てエラーが発生します。を呼び出そうとすると、実行時に 'no such property in undefined' エラーが発生する可能性が高くなりますx[ffVersion][isIE]()。die は、resultOfOperation()isNaNでない限り呼び出さTHINGS_TO_EATれ、 の結果が割り当てられdie()ます。なんで?

JavaScript では、ステートメントの存在を安全に推測できると判断した場合を除き、ステートメントをセミコロンで終了する必要があります。これらの各例では、関数宣言、オブジェクトまたは配列リテラルがステートメント内で使用されています。閉じ括弧は、ステートメントの終わりを示すのに十分ではありません。次のトークンが中置演算子またはブラケット演算子である場合、Javascript はステートメントを終了しません。

これは人々を驚かせたので、割り当ては必ずセミコロンで終わらせてください。

于 2012-10-31T17:58:56.557 に答える