jQuery Mobile:文档准备就绪 vs. 页面事件

273

我正在使用jQuery Mobile,但我不太理解传统的document ready和jQuery Mobile页面事件之间的区别。

  1. 它们之间的真正区别是什么?

    为什么应该选择一个而不是另一个?

  2. 我该如何在我的代码中使用它们?

<!-- language: lang-js -->

$(document).ready() {

});

比...更好

$(document).on('pageinit') {

});
  • 当您从一个页面转换到另一个页面时,页面事件的顺序是什么?

  • 我如何从一个页面发送数据到另一个页面,并且是否可以访问上一个页面的数据?


  • 在问题1下,它们是相同的。您能否进行修改或更详细地解释一下您的意思? - Kirk
    不到一年的时间,关于pageinit事件,“该事件已在1.4.0中被弃用,取而代之的是pagecreate”。请参阅http://api.jquerymobile.com/pageinit/。 - DOK
    5个回答

    438

    jQuery Mobile 1.4更新:

    我的原始文章是针对旧的页面处理方式,基本上是在jQuery Mobile 1.4之前的所有内容。旧的处理方式现已弃用,并将保持活动状态,直到(包括)jQuery Mobile 1.5,因此您仍然可以使用下面提到的所有内容,至少在明年和jQuery Mobile 1.6之前。

    旧事件,包括pageinit不再存在,它们被pagecontainer小部件所取代。 pageinit完全被删除,您可以使用pagecreate代替,该事件保持不变,不会更改。

    如果您对新的页面事件处理方式感兴趣,请查看这里,否则请继续阅读本文。即使您正在使用jQuery Mobile 1.4+,您也应该阅读本答案,因为它超越了页面事件,因此您可能会发现许多有用的信息。

    旧内容:

    本文也可在我的博客HERE中找到。

    $(document).on('pageinit') vs $(document).ready()

    在学习jQuery时,你首先要做的就是调用$(document).ready()函数内的代码,以便在DOM加载后立即执行所有操作。然而,在jQuery Mobile中,使用Ajax将每个页面的内容加载到DOM中,因此$(document).ready()将在加载第一个页面之前触发,并且所有旨在进行页面操作的代码都将在页面刷新后执行。这可能是一个非常微妙的错误。在某些系统上,它可能看起来工作正常,但在其他系统上,它可能会导致不规则、难以重复的怪异现象发生。

    经典的jQuery语法:

    $(document).ready(function() {
    
    });
    

    为了解决这个问题(相信我,这是一个问题),jQuery Mobile开发人员创建了页面事件。简而言之,页面事件是在页面执行的特定点触发的事件。其中一个页面事件是pageinit事件,我们可以像这样使用它:
    $(document).on('pageinit', function() {
    
    });
    

    我们可以采用页面id代替文档选择器,例如我们有一个使用jQuery Mobile的页面,其id为index:

    <div data-role="page" id="index">
        <div data-theme="a" data-role="header">
            <h3>
                First Page
            </h3>
            <a href="#second" class="ui-btn-right">Next</a>
        </div>
    
        <div data-role="content">
            <a href="#" data-role="button" id="test-button">Test button</a>
        </div>
    
        <div data-theme="a" data-role="footer" data-position="fixed">
    
        </div>
    </div>
    

    为了执行仅在索引页面可用的代码,我们可以使用以下语法:
    $('#index').on('pageinit', function() {
    
    });
    
    Pageinit事件将在每次页面即将被加载和显示时执行。除非手动刷新页面或关闭Ajax页面加载,否则不会再次触发。如果您希望代码在每次访问页面时都执行,则最好使用pagebeforeshow事件。

    这里有一个可行的示例:http://jsfiddle.net/Gajotres/Q3Usv/,以演示此问题。

    关于这个问题还有一些注意事项。无论您是使用1个HTML多个页面还是多个HTML文件范例,建议将所有自定义JavaScript页面处理分离到单独的JavaScript文件中。这不会使您的代码更好,但您将拥有更好的代码概述,特别是在创建jQuery Mobile应用程序时。

    还有另一个特殊的 jQuery Mobile 事件,称为mobileinit。 当启动 jQuery Mobile 时,它会在文档对象上触发一个 mobileinit 事件。 要覆盖默认设置,请将它们绑定到 mobileinit。 使用 mobileinit 的一个很好的例子是关闭 Ajax 页面加载或更改默认的 Ajax 加载器行为。

    $(document).on("mobileinit", function(){
      //apply overrides here
    });
    

    页面事件过渡顺序

    首先,所有事件可以在此找到:http://api.jquerymobile.com/category/events/

    假设我们有一个页面A和一个页面B,这是卸载/加载的顺序:

    1. 页面B - 事件 pagebeforecreate

    2. 页面B - 事件 pagecreate

    3. 页面B - 事件 pageinit

    4. 页面A - 事件 pagebeforehide

    5. 页面A - 事件 pageremove

    6. 页面A - 事件 pagehide

    7. 页面B - 事件 pagebeforeshow

    8. 页面B - 事件 pageshow

    为了更好地理解页面事件,请阅读以下内容:

    • pagebeforeloadpageloadpageloadfailed在加载外部页面时触发。
    • pagebeforechangepagechangepagechangefailed是页面更改事件。这些事件在用户在应用程序中导航页面时触发。
    • pagebeforeshowpagebeforehidepageshowpagehide是页面转换事件。这些事件在转换之前、期间和之后被触发,并被命名。
    • pagebeforecreatepagecreatepageinit用于页面初始化。
    • pageremove可以在从DOM中删除页面时触发并处理。

    页面加载jsFiddle示例: http://jsfiddle.net/Gajotres/QGnft/

    如果未启用AJAX,某些事件可能无法触发。

    防止页面转换

    如果由于某种原因需要在某些条件下防止页面转换,则可以使用以下代码:

    $(document).on('pagebeforechange', function(e, data){
        var to = data.toPage,
            from = data.options.fromPage;
    
        if (typeof to  === 'string') {
            var u = $.mobile.path.parseUrl(to);
            to = u.hash || '#' + u.pathname.substring(1);
            if (from) from = '#' + from.attr('id');
    
            if (from === '#index' && to === '#second') {
                alert('Can not transition from #index to #second!');
                e.preventDefault();
                e.stopPropagation();
    
                // remove active status on a button, if transition was triggered with a button
                $.mobile.activePage.find('.ui-btn-active').removeClass('ui-btn-active ui-focus ui-btn');;
            }
        }
    });
    

    这个例子在任何情况下都能工作,因为它会在每个页面转换的开始触发,并且最重要的是,在页面转换发生之前它将防止页面更改。
    以下是一个可行的示例:

    防止多次事件绑定/触发

    jQuery Mobile 的工作方式与传统的 Web 应用程序不同。根据您如何绑定事件,每次访问某个页面时,它都会重复绑定事件。这不是错误,而是 jQuery Mobile 处理其页面的方式。例如,请看以下代码片段:
    $(document).on('pagebeforeshow','#index' ,function(e,data){
        $(document).on('click', '#test-button',function(e) {
            alert('Button click');
        });
    });
    

    工作中的jsFiddle示例:http://jsfiddle.net/Gajotres/CCfL4/

    每次访问页面#index时,点击事件将会绑定到按钮#test-button。通过从页面1到页面2并多次返回来测试它。有几种方法可以解决这个问题:

    解决方案1

    最好的解决方法是使用pageinit来绑定事件。如果您查看官方文档,您会发现pageinit仅会触发一次,就像文档准备好一样,因此不会再次绑定事件。这是最佳解决方案,因为您不需要像使用off方法删除事件那样进行处理开销。

    工作中的jsFiddle示例:http://jsfiddle.net/Gajotres/AAFH8/

    这个工作解决方案是基于以前有问题的示例制作的。

    解决方案2

    在绑定事件之前先移除它:

    $(document).on('pagebeforeshow', '#index', function(){
        $(document).off('click', '#test-button').on('click', '#test-button',function(e) {
            alert('Button click');
        });
    });
    

    以下是一个可以运行的 jsFiddle 示例: http://jsfiddle.net/Gajotres/K8YmG/

    解决方案 3

    使用 jQuery 过滤选择器,如下所示:

    $('#carousel div:Event(!click)').each(function(){
        //If click is not bind to #carousel div do something
    });
    

    因为事件过滤器不是官方jQuery框架的一部分,所以可以在这里找到:http://www.codenothing.com/archives/2009/event-filter/ 简而言之,如果速度是您的主要关注点,则解决方案2比解决方案1好得多。

    解决方案4

    一个新的解决方案,可能是最简单的。
    $(document).on('pagebeforeshow', '#index', function(){
        $(document).on('click', '#test-button',function(e) {
            if(e.handled !== true) // This will prevent event triggering more than once
            {
                alert('Clicked');
                e.handled = true;
            }
        });
    });
    

    工作的jsFiddle示例:http://jsfiddle.net/Gajotres/Yerv9/

    感谢sholsinger提供的解决方案:http://sholsinger.com/archive/2011/08/prevent-jquery-live-handlers-from-firing-multiple-times/

    页面更改事件怪异行为-触发两次

    有时,页面更改事件会触发两次,这与前面提到的问题无关。

    页面beforechange事件发生两次的原因是由于在changePage函数中,当toPage不是jQuery增强型DOM对象时,会发生递归调用。这种递归很危险,因为开发人员可以在事件中更改toPage。如果开发人员始终将toPage设置为字符串,无论它是否是一个对象,在pagebeforechange事件处理程序中,都会导致无限递归循环。pageload事件将新页面作为数据对象的page属性传递(应将此添加到文档中,目前未列出)。因此,可以使用pageload事件来访问已加载的页面。

    简而言之,这是因为您通过pageChange发送了额外的参数。

    示例:

    <a data-role="button" data-icon="arrow-r" data-iconpos="right" href="#care-plan-view?id=9e273f31-2672-47fd-9baa-6c35f093a800&amp;name=Sat"><h3>Sat</h3></a>
    

    要解决这个问题,请使用在页面事件转换顺序中列出的任何页面事件。

    页面更改时间

    如上所述,当您从一个jQuery Mobile页面切换到另一个页面时,通常是通过单击链接到DOM中已存在的另一个jQuery Mobile页面,或通过手动调用$.mobile.changePage来实现的,会发生几个事件和后续操作。在高层次上,以下操作发生:

    • 开始页面更改过程
    • 加载新页面
    • 为该页面的内容进行“增强”(样式化)
    • 从现有页面向新页面进行转换(滑动/弹出等)

    这是一个平均页面转换基准:

    页面加载和处理:3毫秒

    页面增强:45毫秒

    转换:604毫秒

    总时间:670毫秒

    *这些值以毫秒为单位。

    因此,您可以看到转换事件几乎占了执行时间的90%。

    页面转换之间的数据/参数操作

    在页面转换期间,可以从一个页面向另一个页面发送参数。这可以通过几种方式实现。

    参考: https://stackoverflow.com/a/13932240/1848600

    解决方案1:

    您可以使用changePage传递值:

    $.mobile.changePage('page2.html', { dataUrl : "page2.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : true, changeHash : true });
    

    并像这样阅读它们:

    $(document).on('pagebeforeshow', "#index", function (event, data) {
        var parameters = $(this).data("url").split("?")[1];;
        parameter = parameters.replace("parameter=","");
        alert(parameter);
    });
    

    示例

    index.html

    <!DOCTYPE html>
      <html>
        <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="widdiv=device-widdiv, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black" />
        <title>
        </title>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
        <script src="http://www.dragan-gaic.info/js/jquery-1.8.2.min.js">
        </script>
        <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
        <script>
            $(document).on('pagebeforeshow', "#index",function () {
                $(document).on('click', "#changePage",function () {
                    $.mobile.changePage('second.html', { dataUrl : "second.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : false, changeHash : true });
                });
            });
    
            $(document).on('pagebeforeshow', "#second",function () {
                var parameters = $(this).data("url").split("?")[1];;
                parameter = parameters.replace("parameter=","");
                alert(parameter);
            });
        </script>
       </head>
       <body>
        <!-- Home -->
        <div data-role="page" id="index">
            <div data-role="header">
                <h3>
                    First Page
                </h3>
            </div>
            <div data-role="content">
              <a data-role="button" id="changePage">Test</a>
            </div> <!--content-->
        </div><!--page-->
    
      </body>
    </html>

    second.html

    <!DOCTYPE html>
      <html>
        <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="widdiv=device-widdiv, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black" />
        <title>
        </title>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
        <script src="http://www.dragan-gaic.info/js/jquery-1.8.2.min.js">
        </script>
        <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
       </head>
       <body>
        <!-- Home -->
        <div data-role="page" id="second">
            <div data-role="header">
                <h3>
                    Second Page
                </h3>
            </div>
            <div data-role="content">
    
            </div> <!--content-->
        </div><!--page-->
    
      </body>
    </html>

    解决方案2:

    或者您可以创建一个持久的JavaScript对象来存储数据。只要使用Ajax加载页面(并且页面没有以任何方式重新加载),该对象将保持活动状态。

    var storeObject = {
        firstname : '',
        lastname : ''
    }
    

    示例:http://jsfiddle.net/Gajotres/9KKbx/

    解决方案3:

    您也可以像这样访问上一页的数据:

    $(document).on('pagebeforeshow', '#index',function (e, data) {
        alert(data.prevPage.attr('id'));
    });
    

    prevPage对象保存了一个完整的前一页。

    Solution 4:

    作为最后的解决方案,我们有一个巧妙的localStorage HTML实现。它只适用于HTML5浏览器(包括Android和iOS浏览器),但所有存储的数据都可以在页面刷新后保持不变。

    if(typeof(Storage)!=="undefined") {
        localStorage.firstname="Dragan";
        localStorage.lastname="Gaic";
    }
    

    示例:http://jsfiddle.net/Gajotres/J9NTr/

    这可能是最好的解决方案,但在某些 iOS 5.X 版本中会失败。这是一个众所周知的错误。

    不要使用 .live() / .bind() / .delegate()

    我忘了提到(感谢 andleer 提醒我),使用 on/off 进行事件绑定/解绑,live/die 和 bind/unbind 已经过时了。

    jQuery 的 .live() 方法在版本 1.3 中引入 API 时被视为救星。在典型的 jQuery 应用程序中,可能会有很多 DOM 操作,挂接和卸载元素可能会变得非常繁琐。.live() 方法使得基于其选择器钩住应用程序的生命周期内的事件成为可能。听起来不错,对吗?但是,.live() 方法非常缓慢。.live() 方法实际上将其事件挂钩到文档对象上,这意味着事件必须从生成事件的元素冒泡到达文档。这可能非常耗时。
    现在它已经被弃用了。jQuery 团队的人不再推荐使用它,我也不推荐。尽管钩住和卸载事件可能会很繁琐,但没有 .live() 方法的代码比有它的代码快得多。

    使用.on()代替.live().on().live()快2-3倍。请查看此事件绑定基准测试:http://jsperf.com/jquery-live-vs-delegate-vs-on/34,从中一切都会变得清晰明了。

    基准测试:

    有一个专门用于基准测试jQuery Mobile页面事件的优秀脚本。它可以在这里找到:https://github.com/jquery/jquery-mobile/blob/master/tools/page-change-time.js。但在您使用之前,我建议您删除其alert通知系统(每个“更改页面”都会通过停止应用程序向您显示这些数据),并将其更改为console.log函数。

    基本上,此脚本将记录所有页面事件,如果您仔细阅读本文(页面事件说明),则会知道jQm花费了多少时间进行页面增强,页面转换等。

    最终注释

    永远,我是说永远阅读官方的 jQuery Mobile 文档。它通常会为您提供所需的信息,并且与其他一些文档不同,这个文档相当好,具有足够的解释和代码示例。

    更改:

    • 30.01.2013 - 添加了一种新的多事件触发预防方法
    • 31.01.2013 - 对页面转换之间的数据/参数操作章节进行了更好的澄清
    • 03.02.2013 - 在页面转换之间的数据/参数操作章节中添加了新的内容/示例
    • 22.05.2013 - 添加了一个页面转换/更改预防解决方案,并添加了链接到官方页面事件API文档
    • 18.05.2013 - 添加了另一种防止多重事件绑定的解决方案

    2
    $().live()在jQuery 1.7中已被弃用并在1.9中删除,因此它确实应该成为任何jQuery Mobile解决方案的一部分。当前jQM 1.7的最小核心版本。 - andleer
    16
    +1 非常有用的页面加载关键行为总结 - D'Arcy Rittich
    2
    pagecreate事件仅在页面首次创建时触发一次。因此,如果我们在pagecreate内部绑定点击事件,它将不会多次触发。这是我在开发应用程序时发现的一些内容。但我们并不能总是使用pagecreate来绑定事件,所以你提供的解决方案是最好的。+1 给予。 - Jay Mayu
    1
    您的代码中出现了两次pageBeforeShow。它分别被列为第5和第8个。这意味着它会被调用两次吗? - Chase Roberts
    那是一个笔误,我已经修正了,pagebeforeshow 只会触发一次。感谢您注意到了它。 - Gajotres
    显示剩余2条评论

    17

    有些人可能会觉得这很有用。只需将其复制粘贴到您的页面上,您就可以在Chrome控制台(Ctrl + Shift + I)中获取事件被触发的序列。

    $(document).on('pagebeforecreate',function(){console.log('pagebeforecreate');});
    $(document).on('pagecreate',function(){console.log('pagecreate');});
    $(document).on('pageinit',function(){console.log('pageinit');});
    $(document).on('pagebeforehide',function(){console.log('pagebeforehide');});
    $(document).on('pagebeforeshow',function(){console.log('pagebeforeshow');});
    $(document).on('pageremove',function(){console.log('pageremove');});
    $(document).on('pageshow',function(){console.log('pageshow');});
    $(document).on('pagehide',function(){console.log('pagehide');});
    $(window).load(function () {console.log("window loaded");});
    $(window).unload(function () {console.log("window unloaded");});
    $(function () {console.log('document ready');});
    

    当页面被卸载时(当您从页面移开时),您将不会在控制台中看到unload事件。请使用以下方式:

    $(window).unload(function () { debugger; console.log("window unloaded");});
    

    你会明白我的意思的。


    4

    这是正确的方法:

    为了执行仅在索引页面可用的代码,我们可以使用以下语法:

    $(document).on('pageinit', "#index",  function() {
        ...
    });
    

    10
    上面的回答已经表达了同样的意思,你觉得呢? :) - Omar
    感谢您提供的快速修复短答案。 :-) - SharpC

    1
    简单来说,jQuery-mobile 中文档就绪和页面事件的区别在于:
    1. 文档就绪事件用于整个HTML页面,

      $(document).ready(function(e) {
          // 你的代码
      });
      
    2. 当有页面事件时,用于处理特定的页面事件:

      <div data-role="page" id="second">
          <div data-role="header">
              <h3>
                  页面标题
              </h3>
          </div>
          <div data-role="content">
              页面内容
          </div> <!--content-->
          <div data-role="footer">
              页面页脚
          </div> <!--footer-->
      </div><!--page-->
      

    您也可以使用document来处理pageinit事件:

    $(document).on('pageinit', "#mypage", function() {
    
    });
    

    -1

    当您使用.on()时,实际上是在使用动态查询。

    另一方面,.ready(如您的情况)是一个静态查询。在使用它时,您可以动态更新数据,而不必等待页面加载。当输入特定值时,您可以将值直接传递到数据库中(如果需要)。

    在表单中输入数据(帐户、帖子甚至评论)时,使用动态查询很常见。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接