Javascript 简单spa中的jquery未触发hashchange事件

Javascript 简单spa中的jquery未触发hashchange事件,javascript,jquery,html,hashchange,Javascript,Jquery,Html,Hashchange,我正在看一本关于SPA的书——单页web应用程序。这很有趣,但我被一段代码挡住了。到目前为止,我明白我在做什么,所以看起来很奇怪,这不是工作 我在页面右下角的聊天点击中触发了一个事件。此事件触发URL哈希的更改,但。。即使我用$(window.bind('hashchange',onHashchange()).trigger('hashchange')将其绑定到窗口,hashchange事件也不会被触发 我需要手动刷新页面以查看聊天从打开更改为关闭,但这不应该发生,因为我希望它由onhashch

我正在看一本关于SPA的书——单页web应用程序。这很有趣,但我被一段代码挡住了。到目前为止,我明白我在做什么,所以看起来很奇怪,这不是工作

我在页面右下角的聊天点击中触发了一个事件。此事件触发URL哈希的更改,但。。即使我用
$(window.bind('hashchange',onHashchange()).trigger('hashchange')将其绑定到窗口,hashchange事件也不会被触发

我需要手动刷新页面以查看聊天从打开更改为关闭,但这不应该发生,因为我希望它由
onhashchange
触发。 你能帮我查出发生了什么事吗

看起来我遗漏了一些东西,但周围的人都抱怨不能与其他浏览器兼容,而在这种情况下,就是不能工作:S

如有任何提示,不胜感激=)

我有以下html代码

<html>
    <head>
        <title>SPA starter</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" href="css/spa.css" type="text/css">
        <link rel="stylesheet" href="css/spa.shell.css" type="text/css">
        <!-- 3rd party lib -->
        <script id="jquery_js" src="js/libs/jquery/jquery.js"></script>
        <script id="janchor_js" src="js/libs/jquery/jquery.uriAnchor.js"></script>
        <!-- my lib -->
        <script id ="spajs" src="js/spa.js"></script>
        <script id="spashelljs" src="js/spa.shell.js"></script>
        <script id="unloader" src="js/js_unloader.js"></script>

        <script id="starter">
            $(function() {
                spa.initModule($('#spa'));
            });
        </script>
    </head>
    <body>
        <div id="spa"></div>
    </body>
</html>
spa.shell.js

var spa = (function() {
    var initModule = function($container) {
        spa.shell.initModule($container);
    };
    return {initModule: initModule};
}());
spa.shell = (function() {
    //----------- BEGIN MODULE SCOPE VARIABLES --------- 
    var configMap = {
        main_html: String() +
                '<div class="spa-shell-head">' +
                '<div class="spa-shell-head-logo"> </div>' +
                '<div class="spa-shell-head-acct"> </div>' +
                '<div class="spa-shell-head-search"> </div>' +
                '</div>' +
                '<div class="spa-shell-main">' +
                '    <div class="spa-shell-main-nav"> </div>' +
                '    <div class="spa-shell-main-content"> </div>' +
                '</div>' +
                '<div class="spa-shell-foot"></div>' +
                '<div class="spa-shell-chat"></div>' +
                '<div class="spa-shell-modal"></div>',
        chat_extend_time: 250,
        chat_retract_time: 300,
        chat_extend_height: 450,
        chat_retract_height: 15,
        chat_extended_title: 'Click to retract',
        chat_retracted_title: 'Click to extend',
        anchor_schema_map: {
            chat: {open: true, closed: true}
        }
    },
    stateMap = {
        $container: null,
        is_chat_retracted: true,
        anchor_map: {}
    },
    jqueryMap = {},
            setJqueryMap, toogleChat, onClickChat,
            copyAnchorMap, changeAnchorPart, onHashchange,
            initModule;

    //----------- END MODULE SCOPE VARIABLES --------- 
    //----------- BEGIN UTILITY METHODS --------- 
    //Return copy of stored anchro map; minimizes overhead
    copyAnchorMap = function() {
        return $.extend(true, {}, stateMap.anchor_map);
    };

    //----------- END UTILITY METHODS --------- 

    //----------- BEGIN DOM METHODS --------- 
    //Begin DOM method /changeAnchorPart/
    changeAnchorPart = function(arg_map) {
        console.log("change anchor part");
        var
                anchor_map_revise = copyAnchorMap(),
                bool_return = true,
                key_name, key_name_dep;
        //BEGIN merge changes into anchor map
        KEYVAL:
                for (key_name in arg_map) {
            if (arg_map.hasOwnProperty(key_name)) {
                //console.log("key_name:= " + key_name);

                //skip dependet keys during iteration
                if (key_name.indexOf('_') === 0) {
                    console.log("key name starts with '_'");
                    continue KEYVAL;
                }
                //update independent key value
                anchor_map_revise[key_name] = arg_map[key_name];
                //update matching dependent key
                key_name_dep = '_' + key_name;
                //console.log("key_name_dep:= " + key_name_dep);
                if (arg_map[key_name_dep]) {
                    //console.log("if");
                    anchor_map_revise[key_name_dep] = arg_map[key_name_dep];
                }
                else {
                    //console.log("else");
                    delete anchor_map_revise[key_name_dep];
                    delete anchor_map_revise['_s' + key_name_dep];
                }
            }
        }
        //END merge changes into anchor map
        //BEGIN ateempt to update URI; revert if not successful
        try {
            console.log("setting anchor");
            $.uriAnchor.setAnchor(anchor_map_revise);
            console.log("set");
        } catch (error) {
            //replace URI with existing state
            $.uriAnchor.setAnchor(stateMap.anchor_map, null, true);
            console.log("changeAnchorPart error :=" + error);
            bool_return = false;
        }
        //END attemp to update URI
        return bool_return;
    };
    //END DOM method /changeAnchorPart/

    //begin DOM method /setJqueryMap/
    setJqueryMap = function() {
        var $container = stateMap.$container;
        jqueryMap = {$container: $container,
            $chat: $container.find('.spa-shell-chat')
        };
    };
    //end DOM method /setJqueryMap/

    //Begin DOM method /toogleChat/
    //
    toogleChat = function(do_extend, callback) {
        var px_chat_ht = jqueryMap.$chat.height(),
                is_open = px_chat_ht === configMap.chat_extend_height,
                is_closed = px_chat_ht === configMap.chat_retract_height,
                is_sliding = !is_open && !is_closed;
        //avoid race condition
        if (is_sliding) {
            console.log('avoid race condition');
            return false;
        }
        //begin chat slider
        if (do_extend) {
            jqueryMap.$chat.animate({height: configMap.chat_extend_height},
            configMap.chat_extend_time, function() {
                jqueryMap.$chat.attr('title', configMap.chat_extended_title);
                stateMap.is_chat_retracted = false;
                if (callback) {
                    callback(jqueryMap.$chat);
                }
            });
            return true;
        }
        //End extend chat slider

        //Begin retract chat slider
        jqueryMap.$chat.animate({height: configMap.chat_retract_height},
        configMap.chat_retract_time, function() {
            jqueryMap.$chat.attr('title', configMap.chat_retracted_title);
            stateMap.is_chat_retracted = true;
            if (callback) {
                callback(jqueryMap.$chat)
            }

        });
        return true;
        //End rectract chat slider  
    };
    //end DOM method /toogleChat/

    //----------- END DOM METHODS --------- 
    //
    //----------- BEGIN EVENT HANDLERS --------- 
    onClickChat = function(event) {
        // console.log(stateMap.is_chat_retracted);
        changeAnchorPart({
            chat: (stateMap.is_chat_retracted ? 'open' : 'closed')
        });
        return false;

    };
    //
    // BEGIN event handler /onHashchange/
    // 
    onHashchange = function(event) {
        console.log("on hash change");
        var
                anchor_map_previous = copyAnchorMap(),
                anchor_map_proposed,
                _s_chat_previous, _s_chat_proposed,
                s_chat_proposed;
        //Attempt to parse anchor
        try {
            anchor_map_proposed = $.uriAnchor.makeAnchorMap();
        } catch (error) {
            console.log("onHashchange error:= " + error)
            $.uriAnchor.setAnchor(anchor_map_previous, null, true);
            return false;
        }
        stateMap.anchor_map = anchor_map_proposed;
        //convenience vars
        _s_chat_previous = anchor_map_previous._s_chat;
        _s_chat_proposed = anchor_map_proposed._s_chat;
        //BEGIN adjust of component if changed
        if (!anchor_map_previous || _s_chat_previous !== _s_chat_proposed) {
            s_chat_proposed = anchor_map_proposed.chat;
            console.log("adjusting components, chat:= " + s_chat_proposed);
            switch (s_chat_proposed) {
                case 'open':
                    toogleChat(true);
                    break;
                case 'closed':
                    toogleChat(false);
                    break;
                default :
                    toogleChat(false);
                    delete anchor_map_proposed.chat;
                    $.uriAnchor.setAnchor(anchor_map_proposed, null, true);
            }
        }
        //END of the adjustment
        return false;
    };
    //END event handler /onHashchange/

    //----------- END EVENT HANDLERS --------- 

    //----------- BEGIN PUBLIC METHODS --------- 
    //Begin Public methods /initModule/
    //
    initModule = function($container) {


        //load HTML and map jQuery collections
        stateMap.$container = $container;
        $container.html(configMap.main_html);
        setJqueryMap();
        //initialize chat slider and bind click handler
        stateMap.is_chat_retracted = true;
        jqueryMap.$chat.attr('title', configMap.chat_retracted_title)
                .click(onClickChat);
        //configure uriAnchor to use our schema
        $.uriAnchor.configModule({
            schema_map: configMap.anchor_schema_map
        });

        //HANDLE URI anchor change events
        //
        if ("onhashchange" in window) {
            console.log('SUPPORTED');
        }

        $(window).bind('hashchange', onHashchange()).trigger('hashchange');


    };
    //End PUBLIC methods /initModule/
    return {initModule: initModule};
    //----------- END PUBLIC METHODS --------- 
}());
*{
    margin : 0;
    padding : 0;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
h1,h2,h3,h4,h5,h6, p{ margin-bottom: 10px;}
o1,ul,dl{list-style-position: inside;}
/** end reset */

/** begin standard selectors */
body{
    font: 13px 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif;
    color: #444;
    background-color: #888;
}
strong{
    font-weight: 800;
    color:#000;
}

/** end standard selectors */

/** begin spa namespace selectors */
#spa{
    position: absolute;
    top:8px;
    left:8px;
    bottom:8px;
    right:8px;

    min-height: 500px;
    min-width: 500px;
    overflow: hidden;

    border-radius: 0 8px 0 8px;
    background-color: #fff;

}
/** end spa namespace selectors */

/** begin utility selectors */
.spa-x-select{}
.spa-x-clearfloat{
    height: 0 !important;
    float: none !important;
    visibility: hidden !important;
    clear: both !important;
}
/** */
.spa-shell-head, .spa-shell-head-logo, .spa-shell-head-acct, .spa-shell-head-search,
.spa-shell-main, .spa-shell-main-content, .spa-shell-main-nav, .spa-shell-foot,
.spa-shell-chat, .spa-shell-modal {
    position: absolute;
}

.spa-shell-head{
    top:0;
    left:0;
    right:0;
    height: 40px;
    background-color: red;
}
.spa-shell-head-logo{
    top: 4px;
    left: 4px;
    height: 32px;
    width: 128px;
    background: orange;
}
.spa-shell-head-acct{
    top:4px;
    right:0;
    width: 64px;
    height: 32px;
    background: green;
}
.spa-shell-head-search{
    top:4px;
    right:64px;
    width: 248px;
    height: 32px;
    background: blue;
}
.spa-shell-main{
    top:40px;
    left:0;
    bottom:40px;
    right:0;
    background-color: #993300;
}
.spa-shell-main-content, .spa-shell-main-nav{
    top:0;
    bottom:0;
}
.spa-shell-main-nav{
    width:250px;
    background: #eee;
}
.spa-x-closed, .spa-shell-main-nav{
    width:0;
}
.spa-shell-main-content{
    left:250px;
    right:0;
    background: #ddd;
}
.spa-x-closed .spa-shell-main-content{
    left:0;
}
.spa-shell-foot{
    bottom:0;
    left:0;
    right:0;
    height:40px;
    background-color: #99ffff;
}
.spa-shell-chat{
    bottom:0;
    right:0;
    width: 300px;
    height: 15px;
    background: burlywood;
    z-index: 1;
    cursor:pointer;
    border-radius: 5px 0 0 0;
}
.spa-shell-modal{
    margin-top:-200px;
    margin-left:-200px;
    top:50%;
    left:50%;
    width:400px;
    height:400px;
    background: #fff;
    border-radius: 3px;
    z-index: 2;
}
spa.shell.css

var spa = (function() {
    var initModule = function($container) {
        spa.shell.initModule($container);
    };
    return {initModule: initModule};
}());
spa.shell = (function() {
    //----------- BEGIN MODULE SCOPE VARIABLES --------- 
    var configMap = {
        main_html: String() +
                '<div class="spa-shell-head">' +
                '<div class="spa-shell-head-logo"> </div>' +
                '<div class="spa-shell-head-acct"> </div>' +
                '<div class="spa-shell-head-search"> </div>' +
                '</div>' +
                '<div class="spa-shell-main">' +
                '    <div class="spa-shell-main-nav"> </div>' +
                '    <div class="spa-shell-main-content"> </div>' +
                '</div>' +
                '<div class="spa-shell-foot"></div>' +
                '<div class="spa-shell-chat"></div>' +
                '<div class="spa-shell-modal"></div>',
        chat_extend_time: 250,
        chat_retract_time: 300,
        chat_extend_height: 450,
        chat_retract_height: 15,
        chat_extended_title: 'Click to retract',
        chat_retracted_title: 'Click to extend',
        anchor_schema_map: {
            chat: {open: true, closed: true}
        }
    },
    stateMap = {
        $container: null,
        is_chat_retracted: true,
        anchor_map: {}
    },
    jqueryMap = {},
            setJqueryMap, toogleChat, onClickChat,
            copyAnchorMap, changeAnchorPart, onHashchange,
            initModule;

    //----------- END MODULE SCOPE VARIABLES --------- 
    //----------- BEGIN UTILITY METHODS --------- 
    //Return copy of stored anchro map; minimizes overhead
    copyAnchorMap = function() {
        return $.extend(true, {}, stateMap.anchor_map);
    };

    //----------- END UTILITY METHODS --------- 

    //----------- BEGIN DOM METHODS --------- 
    //Begin DOM method /changeAnchorPart/
    changeAnchorPart = function(arg_map) {
        console.log("change anchor part");
        var
                anchor_map_revise = copyAnchorMap(),
                bool_return = true,
                key_name, key_name_dep;
        //BEGIN merge changes into anchor map
        KEYVAL:
                for (key_name in arg_map) {
            if (arg_map.hasOwnProperty(key_name)) {
                //console.log("key_name:= " + key_name);

                //skip dependet keys during iteration
                if (key_name.indexOf('_') === 0) {
                    console.log("key name starts with '_'");
                    continue KEYVAL;
                }
                //update independent key value
                anchor_map_revise[key_name] = arg_map[key_name];
                //update matching dependent key
                key_name_dep = '_' + key_name;
                //console.log("key_name_dep:= " + key_name_dep);
                if (arg_map[key_name_dep]) {
                    //console.log("if");
                    anchor_map_revise[key_name_dep] = arg_map[key_name_dep];
                }
                else {
                    //console.log("else");
                    delete anchor_map_revise[key_name_dep];
                    delete anchor_map_revise['_s' + key_name_dep];
                }
            }
        }
        //END merge changes into anchor map
        //BEGIN ateempt to update URI; revert if not successful
        try {
            console.log("setting anchor");
            $.uriAnchor.setAnchor(anchor_map_revise);
            console.log("set");
        } catch (error) {
            //replace URI with existing state
            $.uriAnchor.setAnchor(stateMap.anchor_map, null, true);
            console.log("changeAnchorPart error :=" + error);
            bool_return = false;
        }
        //END attemp to update URI
        return bool_return;
    };
    //END DOM method /changeAnchorPart/

    //begin DOM method /setJqueryMap/
    setJqueryMap = function() {
        var $container = stateMap.$container;
        jqueryMap = {$container: $container,
            $chat: $container.find('.spa-shell-chat')
        };
    };
    //end DOM method /setJqueryMap/

    //Begin DOM method /toogleChat/
    //
    toogleChat = function(do_extend, callback) {
        var px_chat_ht = jqueryMap.$chat.height(),
                is_open = px_chat_ht === configMap.chat_extend_height,
                is_closed = px_chat_ht === configMap.chat_retract_height,
                is_sliding = !is_open && !is_closed;
        //avoid race condition
        if (is_sliding) {
            console.log('avoid race condition');
            return false;
        }
        //begin chat slider
        if (do_extend) {
            jqueryMap.$chat.animate({height: configMap.chat_extend_height},
            configMap.chat_extend_time, function() {
                jqueryMap.$chat.attr('title', configMap.chat_extended_title);
                stateMap.is_chat_retracted = false;
                if (callback) {
                    callback(jqueryMap.$chat);
                }
            });
            return true;
        }
        //End extend chat slider

        //Begin retract chat slider
        jqueryMap.$chat.animate({height: configMap.chat_retract_height},
        configMap.chat_retract_time, function() {
            jqueryMap.$chat.attr('title', configMap.chat_retracted_title);
            stateMap.is_chat_retracted = true;
            if (callback) {
                callback(jqueryMap.$chat)
            }

        });
        return true;
        //End rectract chat slider  
    };
    //end DOM method /toogleChat/

    //----------- END DOM METHODS --------- 
    //
    //----------- BEGIN EVENT HANDLERS --------- 
    onClickChat = function(event) {
        // console.log(stateMap.is_chat_retracted);
        changeAnchorPart({
            chat: (stateMap.is_chat_retracted ? 'open' : 'closed')
        });
        return false;

    };
    //
    // BEGIN event handler /onHashchange/
    // 
    onHashchange = function(event) {
        console.log("on hash change");
        var
                anchor_map_previous = copyAnchorMap(),
                anchor_map_proposed,
                _s_chat_previous, _s_chat_proposed,
                s_chat_proposed;
        //Attempt to parse anchor
        try {
            anchor_map_proposed = $.uriAnchor.makeAnchorMap();
        } catch (error) {
            console.log("onHashchange error:= " + error)
            $.uriAnchor.setAnchor(anchor_map_previous, null, true);
            return false;
        }
        stateMap.anchor_map = anchor_map_proposed;
        //convenience vars
        _s_chat_previous = anchor_map_previous._s_chat;
        _s_chat_proposed = anchor_map_proposed._s_chat;
        //BEGIN adjust of component if changed
        if (!anchor_map_previous || _s_chat_previous !== _s_chat_proposed) {
            s_chat_proposed = anchor_map_proposed.chat;
            console.log("adjusting components, chat:= " + s_chat_proposed);
            switch (s_chat_proposed) {
                case 'open':
                    toogleChat(true);
                    break;
                case 'closed':
                    toogleChat(false);
                    break;
                default :
                    toogleChat(false);
                    delete anchor_map_proposed.chat;
                    $.uriAnchor.setAnchor(anchor_map_proposed, null, true);
            }
        }
        //END of the adjustment
        return false;
    };
    //END event handler /onHashchange/

    //----------- END EVENT HANDLERS --------- 

    //----------- BEGIN PUBLIC METHODS --------- 
    //Begin Public methods /initModule/
    //
    initModule = function($container) {


        //load HTML and map jQuery collections
        stateMap.$container = $container;
        $container.html(configMap.main_html);
        setJqueryMap();
        //initialize chat slider and bind click handler
        stateMap.is_chat_retracted = true;
        jqueryMap.$chat.attr('title', configMap.chat_retracted_title)
                .click(onClickChat);
        //configure uriAnchor to use our schema
        $.uriAnchor.configModule({
            schema_map: configMap.anchor_schema_map
        });

        //HANDLE URI anchor change events
        //
        if ("onhashchange" in window) {
            console.log('SUPPORTED');
        }

        $(window).bind('hashchange', onHashchange()).trigger('hashchange');


    };
    //End PUBLIC methods /initModule/
    return {initModule: initModule};
    //----------- END PUBLIC METHODS --------- 
}());
*{
    margin : 0;
    padding : 0;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
h1,h2,h3,h4,h5,h6, p{ margin-bottom: 10px;}
o1,ul,dl{list-style-position: inside;}
/** end reset */

/** begin standard selectors */
body{
    font: 13px 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif;
    color: #444;
    background-color: #888;
}
strong{
    font-weight: 800;
    color:#000;
}

/** end standard selectors */

/** begin spa namespace selectors */
#spa{
    position: absolute;
    top:8px;
    left:8px;
    bottom:8px;
    right:8px;

    min-height: 500px;
    min-width: 500px;
    overflow: hidden;

    border-radius: 0 8px 0 8px;
    background-color: #fff;

}
/** end spa namespace selectors */

/** begin utility selectors */
.spa-x-select{}
.spa-x-clearfloat{
    height: 0 !important;
    float: none !important;
    visibility: hidden !important;
    clear: both !important;
}
/** */
.spa-shell-head, .spa-shell-head-logo, .spa-shell-head-acct, .spa-shell-head-search,
.spa-shell-main, .spa-shell-main-content, .spa-shell-main-nav, .spa-shell-foot,
.spa-shell-chat, .spa-shell-modal {
    position: absolute;
}

.spa-shell-head{
    top:0;
    left:0;
    right:0;
    height: 40px;
    background-color: red;
}
.spa-shell-head-logo{
    top: 4px;
    left: 4px;
    height: 32px;
    width: 128px;
    background: orange;
}
.spa-shell-head-acct{
    top:4px;
    right:0;
    width: 64px;
    height: 32px;
    background: green;
}
.spa-shell-head-search{
    top:4px;
    right:64px;
    width: 248px;
    height: 32px;
    background: blue;
}
.spa-shell-main{
    top:40px;
    left:0;
    bottom:40px;
    right:0;
    background-color: #993300;
}
.spa-shell-main-content, .spa-shell-main-nav{
    top:0;
    bottom:0;
}
.spa-shell-main-nav{
    width:250px;
    background: #eee;
}
.spa-x-closed, .spa-shell-main-nav{
    width:0;
}
.spa-shell-main-content{
    left:250px;
    right:0;
    background: #ddd;
}
.spa-x-closed .spa-shell-main-content{
    left:0;
}
.spa-shell-foot{
    bottom:0;
    left:0;
    right:0;
    height:40px;
    background-color: #99ffff;
}
.spa-shell-chat{
    bottom:0;
    right:0;
    width: 300px;
    height: 15px;
    background: burlywood;
    z-index: 1;
    cursor:pointer;
    border-radius: 5px 0 0 0;
}
.spa-shell-modal{
    margin-top:-200px;
    margin-left:-200px;
    top:50%;
    left:50%;
    width:400px;
    height:400px;
    background: #fff;
    border-radius: 3px;
    z-index: 2;
}

如果我错了,请纠正我,但您正在绑定中执行函数。您希望它是这样的:

    $(window).bind('hashchange', onHashchange).trigger('hashchange');
没有后面的“()”

请记住,在某些浏览器中对此的支持是有限的

摘录自:


根据您的需要进行调整。

正如您所见,我已经阅读了您建议的链接,实际上我正在测试“console.log('SUPPORTED');”的日志记录。。我会尽快删除额外的父母身份,并让你知道这是否是错误的。提前感谢您的帮助=)太好了!谢谢你的帮助!括号放错地方了,我不知道怎么放在那里。。谢谢你的帮助,现在一切正常!