如何在HTML5历史状态中存储函数

时间:2012-01-20 06:28:40

标签: javascript html5 browser-history

所以我正在使用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调用)时,浏览器会提出投诉,即错误:“DATA_CLONE_ERR:DOM例外25”

有谁知道我做错了什么?是否可以在州内存储函数调用?

3 个答案:

答案 0 :(得分:7)

不,这是不可能的,不是直接的。 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 )

答案 1 :(得分: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并从数据对象中查找它。

答案 2 :(得分:0)

以下是我的工作:

  • 每个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()函数。这种方法可以在页面加载后继续存在。