jQuery Mobile:文档就绪与页面事件 $(document).on('pageinit')与$(document).ready()

我正在使用jQuery Mobile,并且我无法理解经典文档就绪和jQuery Mobile页面事件之间的差异。

  1. 真正的区别是什么?

    为什么应该

    <!-- language: lang-js -->
    
    $(document).ready() {
    
    });
    

    $(document).on('pageinit') {
    
    });
    
  2. 当您从一个页面转换到另一个页面时,页面事件的顺序是什么?

  3. 如何将数据从一个页面发送到另一个页面,是否可以访问上一页的数据?


答案 1

jQuery Mobile 1.4 更新:

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

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

如果您对页面事件处理的新方法感兴趣,请查看此处,在任何其他情况下,请随时继续阅读本文。即使您使用的是jQuery Mobile 1.4 +,您也应该阅读此答案,它超出了页面事件的范围,因此您可能会找到很多有用的信息。

旧内容:

这篇文章也可以作为我的博客的一部分 在这里找到。

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

你在jQuery中学到的第一件事是调用$(document).ready()函数中的代码,这样一切都会在加载DOM后立即执行。但是,在jQuery Mobile中,Ajax用于在您导航时将每个页面的内容加载到DOM中。因此,$(document).ready() 将在加载第一页之前触发,并且用于页面操作的每个代码都将在页面刷新后执行。这可能是一个非常微妙的错误。在某些系统上,它可能看起来工作正常,但在其他系统上,它可能会导致不稳定,难以重复的怪异现象发生。

经典 jQuery 语法:

$(document).ready(function() {

});

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

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

});

我们可以更进一步,使用页面ID而不是文档选择器。假设我们有一个带有id索引的jQuery Mobile页面:

<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 页面加载,否则它不会再次触发。如果您希望每次访问页面时都执行代码,最好在页面显示事件之前使用页面

下面是一个工作示例:http://jsfiddle.net/Gajotres/Q3Usv/ 来演示此问题。

关于这个问题的更多说明。无论您使用的是1 html多页还是多HTML文件范式,建议将所有自定义JavaScript页面处理分离到一个单独的JavaScript文件中。这将注意到使您的代码更好,但您将拥有更好的代码概述,尤其是在创建jQuery移动应用程序时。

还有另一个特殊的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 - 活动页面创建之前

  2. 页面 B - 事件页面创建

  3. 页面 B - 事件页面初始化

  4. 页面 A - 活动页面在海德之前

  5. 页面 A - 活动页面删除

  6. 页面 A - 事件页面隐藏

  7. B 页 - 活动页前显示

  8. 页面 B - 活动页面显示

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

  • 页面之前加载页面加载页面加载失败在加载外部页面时触发
  • 页面更改之前页面更改页面更改是页面更改事件。当用户在应用程序中的页面之间导航时,将触发这些事件。
  • pagebeforeshowpagebeforehidepageshowpagehide是页面过渡事件。这些事件在转换之前、期间和之后触发并命名。
  • pagebeforecreatepagecreatepageinit用于页面初始化。
  • 可以触发页面删除,然后在从 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按钮。通过从第 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/

Tnx 到该解决方案的 sholsingerhttp://sholsinger.com/archive/2011/08/prevent-jquery-live-handlers-from-firing-multiple-times/

页面更改事件怪癖 - 触发两次

有时页面更改事件可以触发两次,它与前面提到的问题没有任何关系。

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

简而言之,发生这种情况是因为您正在通过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 ms

总时间: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);
});

示例

索引.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>

第二.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 对象包含完整的上一页。

解决方案 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()

我忘了提到(和tnx 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。但是在您使用它做任何事情之前,我建议您删除其警报通知系统(每个“更改页面”将通过停止应用程序向您显示此数据),并将其更改为控制台.log功能。

基本上,此脚本将记录您的所有页面事件,如果您仔细阅读本文(页面事件描述),您将知道jQm在页面增强功能,页面过渡方面花费了多少时间....

结语

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

变化:

  • 30.01.2013 - 添加了多事件触发预防的新方法
  • 31.01.2013 - 为页面过渡之间的数据/参数操作章节添加了更好的说明
  • 03.02.2013 - 在页面过渡之间的数据/参数操作一章中添加了新内容/示例
  • 22.05.2013 - 添加了页面转换/更改预防解决方案,并添加了指向官方页面事件 API 文档的链接
  • 18.05.2013 - 添加了针对多事件绑定的另一个解决方案

答案 2

你们中的一些人可能会发现这很有用。只需将其复制粘贴到您的页面,您将获得一个序列,其中事件在Chrome控制台中触发( + + )。CtrlShiftI

$(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');});

您不会在控制台中看到卸载,因为它在卸载页面时(当您离开页面时)被触发。像这样使用它:

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

你会明白我的意思。