1

我有一个 Web 应用程序,我在其中加载(通过 ajax)一个字典文件(1MB)到 javascript 数组中。我找到了Mobile Safari 在 10 秒后崩溃的原因。但现在我想知道的是如何解决这个问题?

在上面的链接上,答案建议使用setInterval,但这意味着我必须将字典文件分块并逐个加载。这当然可以做到,但是考虑到互联网速度,我必须制作很多块,并且页面加载需要永远加载太多请求(如果我让块太大,一些移动用户可能会发生这种情况将无法在给定的 10 秒内下载该块)。

所以,我的问题是:有没有人遇到过这种问题,你是怎么解决的?对正确方向的普遍推动表示赞赏。

编辑:这是我用来加载字典的 js 代码:

var dict = new Trie();

$.ajax({
    url: 'data/dictionary_342k_uppercase.txt',
    async: true,
    success: function (data) {
        var words = data.split('\n');
        for (var i = words.length - 1; i >= 0; i--) {
            dict.insert(words[i]);
        }           
    },
    error: function(){
        $('#loading-message').text("Problem s rječnikom");
    }
});

Trie.js:

function Trie () {
  var ALPHABET_SIZE = 30;
  var ASCII_OFFSET = 'A'.charCodeAt();

  this.children = null;
  this.isEndOfWord = false;

  this.contains = function (str) {
    var curNode = this;

    for (var i = 0; i < str.length; i++) {
      var idx = str.charCodeAt(i) - ASCII_OFFSET;
      if (curNode.children && curNode.children[idx]) {
        curNode = curNode.children[idx];
      } else {
        return false;
      }
    }

    return curNode.isEndOfWord;
  }

  this.has = function (ch) {
    if (this.children) {
      return this.children[ch.charCodeAt() - ASCII_OFFSET] != undefined;
    }
    return false;
  }

  this.next = function (ch) {
    if (this.children) {
      return this.children[ch.charCodeAt() - ASCII_OFFSET];
    }
    return undefined;
  }

  this.insert = function (str) {
    var curNode = this;

    for (var i = 0; i < str.length; i++) {
      var idx = str.charCodeAt(i) - ASCII_OFFSET;

      if (curNode.children == null) {
        curNode.children = new Array(ALPHABET_SIZE);
        curNode = curNode.children[idx] = new Trie();
      } else if (curNode.children[idx]) {
        curNode = curNode.children[idx];
      } else {
        curNode = curNode.children[idx] = new Trie();
      }
    }

    curNode.isEndOfWord = true;
    return curNode;
  }
}
4

2 回答 2

7

一旦您开始在 JS 中进行处理,这是一个非常常见的问题。如果 Mobile Safari 问题是原因,那么您要做的就是找出 CPU 时间的去向。

我假设它是dict.insert()循环而不是data.split()调用(这将更难以管理)。

这里的想法是将dict.insert()循环拆分为可以在顺序循环中异步调用的功能块(这就是setupBuildActions函数的作用)。在第一个块之后,每个后续块都被调用 via setTimeout,这有效地重置了 JS 运行时中的函数时间计数器(这似乎是杀死你的进程的原因)。

使用 Sequencer 函数意味着您还可以控制函数的运行顺序(它们始终按照它们在此处生成的顺序运行,并且不会同时安排两个或多个函数执行)。setTimeout这比在没有回调的情况下触发数千个调用要有效得多。您的代码保留对执行顺序的控制(这也意味着您可以在执行期间进行更改)并且 JS 运行时不会因预定的执行请求而过载。

您可能还想查看https://github.com/michiel/sequencer-js上的节点项目以获取更多排序示例,并查看http://ejohn.org/blog/how-javascript-timers-work/以获取关于setTimeout在不同的平台上。

var dict = new Trie();

// These vars are accessible from all the other functions we're setting up and
// running here

var BLOCKSIZE     = 500;
var words         = [];
var buildActions  = [];

function Sequencer(funcs) {
  (function() {
    if (funcs.length !== 0) {
      funcs.shift()(arguments.callee);
    }
  })();
}

// Build an Array with functions that can be called async (using setTimeout)

function setupBuildActions() {
  for (var offset=0; offset<words.length; offset+= BLOCKSIZE) {
    buildActions.push((function(offset) {
      return function(callback) {
        for (var i=offset; i < offset + BLOCKSIZE ; i++) {
          if (words[i] !== null) { // ugly check for code brevity
            dict.insert(words[i]);
          }
        }           
        // This releases control before running the next dict.insert loop
        setTimeout(callback, 0);
      };
    })(offset));
  }
}

$.ajax({
    url: 'data/dictionary_342k_uppercase.txt',
    async: true,
    success: function (data) {
      // You might want to split and setup these calls 
      // in a setTimeout if the problem persists and you need to narrow it down
      words = data.split('\n');
      setupBuildActions();
      new Sequencer(buildActions);
    },
    error: function(){
      $('#loading-message').text("Problem s rječnikom");
    }
});
于 2013-04-22T22:11:30.543 回答
0

这是一个使用 setTimeout 将单词实际插入到您的 trie 中的示例。它将原始字符串分解成批次,并使用 setTimeout 来延迟插入每批次单词的处理。我的示例中的批量大小是 5 个字。

实际的批量插入作为浏览器中的后续事件处理程序发生。

仅仅将单词分成几批可能会花费太长时间。如果遇到此问题,请记住您可以链接 setTimeout() 调用,例如迭代一段时间然后使用 setTimeout 安排另一个事件进行更多迭代,然后再次 setTimeout 等等。

    function addBatch(batch)
    {
            console.log("Processing batch:");
            for (var i = 0; i < batch.length; i++)
                    console.log(batch[i]);
            console.log("Return from processing batch");
    }

    var str = "alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot\n" + 
              "golf\nhotel\nindia\njuliet\nkilo\nlima\n" + 
              "mike\nnovember\noscar\npapa\nquebec\n" + 
              "romeo\nsierra\ntango\nuniform\n" + 
              "victor\nwhiskey\nxray\nyankee\nzulu";

    var batch = []
    var wordend;
    for (var wordstart = 0; wordstart < str.length; wordstart = wordend+1)
    {
            wordend = str.indexOf("\n", wordstart);
            if (wordend < 0)
                    wordend = str.length;
            var word = str.substring(wordstart, wordend);
            batch.push(word);

            if (batch.length > 5)
            {
                    setTimeout(addBatch, 0, batch);
                    batch = [ ];
            }
    }
    setTimeout(addBatch, 0, batch);
    batch = [ ];
于 2013-04-22T01:31:03.580 回答