【问题标题】:How can I store functions within the HTML5 history states如何在 HTML5 历史状态中存储函数
【发布时间】:2012-01-20 06:28:40
【问题描述】:

因此,我使用 HTML5 历史管理来添加在带有 AJAX 加载的子内容的网站内来回导航的功能。

现在我想在状态对象中存储 javascript 函数,以便在状态弹出时回调。或多或少像下面的代码:

$(window).bind("popstate", function(event) {
    var state = event.originalEvent.state;
    if (!state) {
        return;
    }
    state.callback(state.argument);
}

function beforeLoad() {
    var resourceId = "xyz";
    var func;

    if (case1) {
        func = switchPageToMode1;
    } else { // case 2
        func = swithPageToMode2;
    }

    func(resourceId); // run the function
    window.history.pushState({ callback: func, resourceId: resourceId }, "newTitle", "newURL"); // and push it to history
}

function switchPageToMode1(resourceId) {
    alterPageLayoutSomeWay();
    loadResource(resourceId);
}

function swithPageToMode2(resourceId) {
    alterPageLayoutSomeOtherWay();
    loadResource(resourceId);
}

function loadResource(resourceId) {
    ...
}

好的。所以我想要做的是存储对 javascript 函数的引用。但是当推送状态(实际的 window.history.pushState 调用)时,浏览器会提出投诉,即 Error: "DATA_CLONE_ERR: DOM Exception 25"

有人知道我做错了什么吗?是否可以在状态中存储函数调用?

【问题讨论】:

    标签: javascript html browser-history


    【解决方案1】:

    我想出了一个稍微不同的解决方案。

    我在窗口变量中添加了两个变量

    window.history.uniqueStateId = 0;
    window.history.data = {}.
    

    每次我执行 pushstate 时,我所做的只是为第一个参数推送一个唯一的 id

    var data = { /* non-serializable data */ };
    window.history.pushState({stateId : uniqueStateId}, '', url);
    window.history.data[uniqueStateId] = data;
    

    在 popstate 事件中,我只需从状态对象中获取 id 并从数据对象中查找它。

    【讨论】:

    • 这实际上是一个时髦但仍然简单的解决方案。喜欢它!
    • -1:这不会在页面重新加载后继续存在。如果您使用后退按钮离开页面,然后使用前进按钮返回页面,您会发现您的所有状态都消失了。
    • 其他答案都没有。如果需要,只需混合一些本地存储解决方案
    【解决方案2】:

    不,这是不可能的,反正也不是直接的。 According to MDC“状态对象”,即pushState的第一个参数,“可以是任何可以序列化的东西。”不幸的是,你不能序列化一个函数。 WHATWG spec 说的基本相同,但用更多的话来说,其要点是在状态对象中明确禁止函数。

    解决方案是将您可以eval 的字符串或函数的名称存储在状态对象中,例如:

    $(window).bind("popstate", function(event) {
      var state = event.originalEvent.state;
    
      if ( !state ) { return; }
    
      window[ state.callback ]( state.argument );  // <-- look here
    }
    
    function beforeLoad() {
      var resourceId = "xyz",
          func
      ;
    
      if ( case1 ) {
        func = "switchPageToMode1";  // <-- string, not function
      } else {
        // case 2
        func = "swithPageToMode2";
      }
    
      window[ func ]( resourceId );  // <-- same here
    
      window.history.pushState(
        { callback : func,
          argument : resourceId
        },
        "newTitle", "newURL"
      );
    }
    

    当然,这是假设 switchPageToMode1-2 在全局上下文中(即 window),这不是最佳做法。如果不是,则必须以某种方式从全局上下文中访问它们,例如[window.]MyAppGlobal.switchPageToMode1,在这种情况下,您将调用 MyAppGlobal[ func ]( argument )

    【讨论】:

    • 非常感谢!这说明了一切;错过了序列化部分。
    • @Jordan 我正在存储一个对象,它看起来像 { html: $('#main').html() },但即使我使用的是 IE 11 和/或添加一些额外的历史 API,例如 @,我仍然在 IE 中收到 Data Clone Error 987654323@。我还能做错什么?
    【解决方案3】:

    这是我的工作:

    • 每个 HTML 页面都包含一个或多个可以创建新历史记录条目的组件。
    • 每个组件实现三种方法:
      1. getId() 返回其唯一的 DOM id。
      2. getState() 返回组件的状态:

        {
          id: getId(),
          state: componentSpecificState
        }
        
      3. setState(state) 使用上述值更新组件的状态。
    • 在页面加载时,我初始化了一个从组件 id 到组件的映射,如下所示:

      this.idToComponent[this.loginForm.getId()] = this.loginForm;
      this.idToComponent[this.signupForm.getId()] = this.signupForm;
      
    • 组件在创建新的历史条目之前保存它们的状态: history.replaceState(this.getState(), title, href);

    • popstate 事件被触发时,我调用:

      var component = this.idToComponent[history.state.id];
      component.setState(history.state);
      

    总结一下:我们不是序列化function(),而是序列化组件ID并触发它的setState()函数。这种方法可以在页面加载后继续存在。

    【讨论】:

      猜你喜欢
      • 2015-06-14
      • 2013-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-21
      • 1970-01-01
      • 1970-01-01
      • 2018-08-07
      相关资源
      最近更新 更多