https://www.html5rocks.com/en/tutorials/appcache/beginner/

http://diveintohtml5.info/offline.html#fallback

介绍

  离线web程序变得非常重要。所有的浏览器都支持长期缓存页面资源,但浏览器会为了腾出缓存空间而清除掉一些之前的缓存。h5中通过ApplicationCache(后面简称appCache)来解决这个问题。appCache可以允许开发者指定某些文件应该保存到本地,用于离线访问。它有三个优点:

  1. 让用户可以离线访问页面
  2. 直接从本地硬盘而不是网络获取资源,速度更快
  3. 当我们的服务器因为某些原因宕机时,用户依然可以访问我们的页面,这使我们的服务更加健壮

清单文件

  这是一个文本文件,用于列出浏览器应该缓存的资源,来离线访问。通过在页面中引用这个清单文件,来启用离线缓存:

  1. <html manifest="example.appcache">
  2. ...
  3. </html>

  每个需要被离线访问的页面都需要像上面那样引用清单文件,如果html标签没有manifest属性,则浏览器不会缓存这个页面。页面地址后有不同参数,会被AppCache认为是不同的页面,会被分开缓存,所以AppCache最好用于无参数(或固定参数,唯一的)地址的页面

  通过在chrome中访问chrome://appcache-internals/来管理被appCache缓存的页面。

  manifest所指向的清单文件地址必须与当前页面同源。清单文件可以使任意后缀,但被服务器输出时必须指定其媒体类型(content-type)为text/cache-manifest。这个规定在较新版本的chrome、ff、Safari被废弃了,但是对于IE11以及其他老浏览器,仍然有这个规定。

  一个简单的清单文件结构如下:

  1. CACHE MANIFEST
  2. index.html
  3. stylesheet.css
  4. images/logo.png
  5. scripts/main.js
  6. http://cdn.example.com/scripts/main.js

  以上清单文件会缓存4个文件,需要注意的是:

  1. CACHE MANIFEST必须在第一行
  2. 被缓存的文件可以来自其他域(CDN)
  3. 一些浏览器对于缓存的数量有限制,在chrome中,appCache使用共享缓存(与其他离线API共享这片区域)
  4. 如果manifest指向的地址返回了404或410,则缓存会被删除
  5. 如果清单文件或者指定被缓存的资源下载失败,则缓存会更新失败,浏览器会使用旧的缓存。

  浏览器会下载所有清单文件中指定的文件,而不管这些文件有没有在当前页面中有用到。如果有多个页面,则每个页面都需要指向相同的、代表了整个应用程序的清单文件。如果app只有一个html,则保证这个html引用了清单文件即可,而不需要把这个html添加到清单文件中,因为浏览器会认为这个页面属于app的一部分,而自动对这个html进行缓存(但还是推荐显式地加上),利用html文件会被自动缓存的这特点可以实现一个“懒缓存”,H5说明书提供了一个例子:

  1. CACHE MANIFEST
  2. FALLBACK:
  3. / /offline.html
  4. NETWORK:
  5. *

  对于一个有成千上万个页面的站点,我们不可能也不想去下载整个站点,但可以将其中一部分做成离线的,但怎么决定哪些页面应该被缓存呢?假设这个巨大的站点支持离线,我们访问的每个页面都会被下载和缓存。这就是以上清单文件所能做的事情。假设这个巨大的站点中每个html都引用了上面的清单文件,当访问里面的html时,浏览器会认为这个html是一个离线app的一部分,如果浏览器没有下载过这个清单文件,则会创建一个新的离线缓存,然后下载清单文件中所有的资源并且缓存到刚刚创建的离线缓存中,然后把当前页面也添加到缓存中。这样一来,任何被访问过的页面都会添加进同一个离线缓存中。换个说法就是所有html都引用同一个清单文件,但CACHE那一块不需要列举html文件,因为会自动缓存,但里面的资源需要列举,因为里面的资源不会被自动缓存。

  以上的fallback仅仅只有一行,第一个斜杠是一个url匹配模式,会匹配所有页面。当我们离线访问一个页面时,这个页面在缓存中,则显示缓存的页面;如果不在缓存中,则显示错误信息,并且显示/offline.html这个页面。

  下面来看一个更复杂的例子( # 用于注释一行):

  1. CACHE MANIFEST
  2. # 2010-06-18:v2
  3.  
  4. # Explicitly cached 'master entries'.
  5. CACHE:
  6. /favicon.ico
  7. index.html
  8. stylesheet.css
  9. images/logo.png
  10. scripts/main.js
  11.  
  12. # Resources that require the user to be online.
  13. NETWORK:
  14. *
  15.  
  16. # static.html will be served if main.py is inaccessible
  17. # offline.jpg will be served in place of all images in images/large/
  18. # offline.html will be served in place of all other .html files
  19. FALLBACK:
  20. /main.py /static.html
  21. images/large/ images/offline.jpg

事件流

  当我们访问了一个页面引用了清单文件,会有一系列的事件在window.applicationCache对象上触发(事件监听器要设置在这个对象上)。

  当浏览器发现html标签上有一个manifest属性时,首先会触发一个checking事件,这个事件总会触发,不管浏览器之前有没有遇到过这个清单文件(再次访问当前页面或者访问过其他页面指向了相同的清单文件)

  假如浏览器第一次遇到这个清单文件:

  1. 触发一个downloading事件,然后下载里面的资源
  2. 正在下载的时候,会持续触发progress事件,可以知道文件的下载情况
  3. 当所有文件都下载完,会触发一个cached事件,这就意味着资源已经缓存完成,可以离线了。

  如果浏览器之前就遇到过这个清单文件,则可能有文件已经缓存好了。那问题就是自上次检查完后,这个清单文件本身有没有发生变化?

  • 如果没有发生变化,则触发一个noupdate事件
  • 如果有,则触发一个downloading事件,然后重新下载里面所有的资源。正在下载时,持续触发progress事件。当全部资源已经重新下载完毕了,触发updateready事件,这意味着一个全新版本的资源已经缓存完成,可以离线了。但新的资源并没有被应用上去,需要调用swapCache函数,然后手动刷新页面才会生效。

  如果以上更新流程出现问题,则触发一个error事件并且停止更新。可能出错的地方:

  1. 可以联网时,以上的协商检查返回了404或410(清单文件找不到或者无权访问清单文件)
  2. 重新绑定关联时,新的清单下载失败
  3. 当更新过程中,服务器的清单文件发生变化
  4. 重新下载所有资源时,资源下载失败。

  appCache暴露了一些事件让我们去观察appCache的状态:

  1. function handleCacheEvent(e) {
  2. //...
  3. }
  4.  
  5. function handleCacheError(e) {
  6. alert('Error: Cache failed to update!');
  7. };
  8.  
  9. // Fired after the first cache of the manifest.
  10. appCache.addEventListener('cached', handleCacheEvent, false);
  11.  
  12. // Checking for an update. Always the first event fired in the sequence.
  13. appCache.addEventListener('checking', handleCacheEvent, false);
  14.  
  15. // An update was found. The browser is fetching resources.
  16. appCache.addEventListener('downloading', handleCacheEvent, false);
  17.  
  18. // The manifest returns 404 or 410, the download failed,
  19. // or the manifest changed while the download was in progress.
  20. appCache.addEventListener('error', handleCacheError, false);
  21.  
  22. // Fired after the first download of the manifest.
  23. appCache.addEventListener('noupdate', handleCacheEvent, false);
  24.  
  25. // Fired if the manifest file returns a 404 or 410.
  26. // This results in the application cache being deleted.
  27. appCache.addEventListener('obsolete', handleCacheEvent, false);
  28.  
  29. // Fired for each resource listed in the manifest as it is being fetched.
  30. appCache.addEventListener('progress', handleCacheEvent, false);
  31.  
  32. // Fired when the manifest resources have been newly redownloaded.
  33. appCache.addEventListener('updateready', handleCacheEvent, false);
  34.  

调试的艺术

  以上提到清单文件中的资源下载失败时,会触发一个error事件,但我们无法得知具体的错误是什么,这使离线应用调试起来让人沮丧。

  浏览器到底是怎么检查被缓存的清单文件是否发生了修改呢?分成三步:

  1. 通过http协议,类似于其他http资源,浏览器会检查缓存的清单文件是否过期,在http响应头中包含了文件的元信息,但不是强缓存(Expires、Cache-Control)。
  2. 假如缓存的清单文件过期了(通过http头检查),浏览器会问服务器是否有一个新的版本可以下载,是则下载。为了实现这一点浏览器会发起一个http请求,里面包含了缓存的清单文件的修改时间,这个时间也就是上次清单文件下载的时间。如果web服务器认为这个文件没有被修改,则返回304.
  3. 如果web服务器认为这个文件发生修改了,则返回200,浏览器从这个响应中读取新的清单文件内容.

  总结以上三个步骤就是缓存的清单文件是协商缓存。

  假如发布了一个清单文件,10分钟后,往上面加了一行,再次发布。会发生这样的事情:刷新页面,结果什么也没发生。这是因为浏览器始终认为这个缓存没有发生变化,这可能是因为服务器设置了强缓存(Cache-Control)。所以有一件事情绝对要去做:取消(不设置)清单文件的强缓存。

  只要清单文件没有发生变化,则即使里面的资源发生了变化,浏览器也不会发现。如一个css文件已经重新发布了,但运行起来没有任何变化,因为清单文件没有发生变化。解决办法就是:只要离线资源发生了变化,就去修改清单文件,简单随便改里面的一个字符即可。最方便就是通过 # 注释往里面标记一个版本号,修改版本号即可

  1. CACHE MANIFEST
  2. # rev 43
  3. clock.js
  4. clock.css

  html标签可以以相对路径引用清单文件,清单文件内以相对于清单文件的路径来引用其他资源。

缓存更新

 可以理解为清单文件也会被缓存,是协商缓存。表现为

  1. 第一次访问页面时,下载并且把清单内容缓存到本地,浏览器再将这个清单文件的地址与页面的地址进行关联(绑定关联)
  2. 再次访问时,浏览器根据页面地址找到对应关联的清单地址(获取关联),然后检查这个地址是否与当前页面的清单地址一致(校验关联),不一致则重新下载新的清单(重新绑定关联)。最后根据这个地址检查本地的清单内容是否发生变化(协商检查),是则进入更新流程(重新下载所有资源),无法联网则使用缓存的清单文件。

  仅当清单文件内容发生变化时,才会触发浏览器去更新缓存。假如清单文件无变化,而仅仅修改了图片或者修改了一个JS函数,这些变化不会被缓存(不会被浏览器发现)。

  在一次更新中,清单文件会被检查(协商检查清单文件有没有发生变化)两次,开始的时候一次和所有缓存文件都更新完成后再检查一次。如果清单文件在更新的时候被修改,即两次检查的结果不一致,则更新失败。

  就算缓存被更新了,浏览器不会使用这些缓存,直到页面被刷新,因为缓存的更新发生在页面重新加载之后(加载的是当前版本的缓存,而不是新版版的缓存)。

  当清单文件或者清单文件内指定的资源下载失败时,整个更新过程就会失败。浏览器会继续使用旧的缓存

  缓存会一直有效,直到:

  1. 用户手动清空浏览器缓存
  2. 清单文件被修改。更新清单文件中的缓存列表,并不意味着浏览器会对缓存进行更新,清单文件本身必须要修改

缓存的状态

  通过window.applicationCache可以以编程方式来访问浏览器中的appCache,对象的status属性用于检查当前缓存的状态

  1. var appCache = window.applicationCache;
  2.  
  3. switch (appCache.status) {
  4. case appCache.UNCACHED: // UNCACHED == 0
  5. return 'UNCACHED';
  6. break;
  7. case appCache.IDLE: // IDLE == 1
  8. return 'IDLE';
  9. break;
  10. case appCache.CHECKING: // CHECKING == 2
  11. return 'CHECKING';
  12. break;
  13. case appCache.DOWNLOADING: // DOWNLOADING == 3
  14. return 'DOWNLOADING';
  15. break;
  16. case appCache.UPDATEREADY: // UPDATEREADY == 4
  17. return 'UPDATEREADY';
  18. break;
  19. case appCache.OBSOLETE: // OBSOLETE == 5
  20. return 'OBSOLETE';
  21. break;
  22. default:
  23. return 'UKNOWN CACHE STATUS';
  24. break;
  25. };

  编程方式来更新缓存首先要调用update方法,这会尝试去更新缓存(但需要清单文件已经发生变化),当status进行UPDATEREADY时,可以使用swapCache函数来将新的缓存替换旧的缓存:

  1. var appCache = window.applicationCache;
  2.  
  3. appCache.update(); // Attempt to update the user's cache.
  4.  
  5. ...
  6.  
  7. if (appCache.status == window.applicationCache.UPDATEREADY) {
  8. appCache.swapCache(); // The fetch was successful, swap in the new cache.
  9. }

  以上缓存替换完成后,需要刷新页面,新替换进去的缓存才会生效,可以这么做:

  1. window.addEventListener('load', function(e) {
  2.  
  3. window.applicationCache.addEventListener('updateready', function(e) {
  4. if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
  5. // Browser downloaded a new app cache.
  6. if (confirm('A new version of this site is available. Load it?')) {
  7. window.location.reload();
  8. }
  9. } else {
  10. // Manifest didn't changed. Nothing new to server.
  11. }
  12. }, false);
  13.  
  14. }, false);

清单文件的结构

  清单文件可以分成三个部分(书写顺序无影响,一个文件中可以同一个部分出现多次):CACHE、NETWORK和FALLBACK。

CACHE:

  这是默认的部分,里面列出的文件(或者直接在CACHE MANIFEST底下的文件)会被下载后第一时间进行缓存

NETWORK:

  里面列出的文件如果不在缓存中,则从网络上下载;否则如果某些资源地址不在这里,就算网络通畅,也不会去下载文件。所以一般指定一个 * 即可。

FALLBACK:

  这是可选的,里面第一个url指定为资源,第二个url为当资源无法访问时就访问这个url。两个url必须与当前页面同源。这里指定资源url时,可以指定一个前缀来批量地指定多个url的访问失败时的反馈页面。

  以下例子定义了一个全资源的错误页面(offline.html),当无网络时,用户访问站点根目录或者访问其他所有资源时,则这个页面就会显示出来

  1. CACHE MANIFEST
  2. # 2010-06-18:v3
  3.  
  4. # Explicitly cached entries
  5. index.html
  6. css/style.css
  7.  
  8. # offline.html will be displayed if the user is offline
  9. FALLBACK:
  10. / /offline.html
  11.  
  12. # All other resources (e.g. sites) require the user to be online.
  13. NETWORK:
  14. *
  15.  
  16. # Additional resources to cache
  17. CACHE:
  18. images/logo1.png
  19. images/logo2.png
  20. images/logo3.png

appCache是一个逗比(douchebag)

http://alistapart.com/article/application-cache-is-a-douchebag

  里面形容appCache就是一颗洋葱,当你一层一层剥开,你会流泪的。

1.文件总是来自于appCache,即使网络可用

  当缓存更新完成,updateready事件会被触发,但这时候我们不能刷新页面,因为用户可能正在进行交互。这不是什么大问题,因为旧的缓存也可能足够好用了,如果实在需要新的缓存,则弹框提醒用户是否要刷新界面来启用新的缓存,是则刷新页面。

2.appCache缓存仅当清单文件本身发生变化时才更新

  http协议是有缓存的,我们可以为每个文件定义缓存的方式(总是不缓存、协商缓存和强缓存)。假如清单文件中有50个html,每次我们访问里面的页面,浏览器就要创建50个请求来检查他们是否需要更新。

  浏览器仅当清单文件本身发生变化了,才去检查内部的资源是否需要更新,只要清单文件的一个字节发生变化,就会触发检查更新。

  这对一直不会变化的静态资源来说非常好。当资源的路径发生变化,就意味着清单文件的内容发生变化。最简单的改变清单文件的方式是修改里面的注释(如# v1 改成 # v2),这可以通过构建工具来完成,类似于为每个文件都创建一个唯一标识符ETAG,然后通过注释写到清单文件中,这样一来,文件内容发生变化,则清单文件发生变化

  然而,清单文件被更新,不意味着里面的资源会更新。

3.appCache属于额外的缓存,不能替换http缓存

  大部分appCache被更新时,浏览器会发出http请求。这符合一般的缓存流程:假如资源已经被强缓存下来了,则appCache更新,浏览器发出请求,发现这个强缓存没有到期,则不会去访问服务器了,更新就这样结束了。这是可以的,因为当清单文件发生变化时,我们降低了向服务器请求的次数。

  所以所有资源都要正确地设置好http缓存头,这比清单文件更加重要

4.永远不要去强缓存清单文件

  强缓存一个清单文件的话,页面每次获取到的清单文件总是旧的,根据清单文件获取的缓存也是旧的。

5.一个缓存页上不会加载没被缓存的资源

  当缓存了index.html,却没有缓存cat.jpg,则这个图片不会显示在index.html上,即使网络可用。解决这个问题可以将NETWORK版块设置为一个 * 。这意味着浏览器在展示一个缓存页时,没有被缓存的资源就从网络中获取。

配合iframe使用

案例1

  在线访问一个普通页面A(没有引用清单文件),内嵌了一个隐藏的iframe,指向另一个页面B(引用了清单文件),可以离线访问B

案例2

  在案例1的基础上,B中的清单文件配置了Fallback,然后离线访问A,B中的fallback生效了。这是因为B在上次已经被缓存,然后离线访问A,A中通过一个隐藏的iframe访问了B,就相当于离线访问了B,B的fallback就生效了。

fallback:

  1. CACHE MANIFEST
  2. FALLBACK:
  3. / fallback.html
  4. /assets/imgs/avatars/ assets/imgs/avatars/default-v1.png

  以上当请求失败,就显示fallback.html。除非前缀为/assert/ims/avatars/,就返回一个默认的图片。

使用localstorage来动态离线管理

  我们不可能缓存所有东西,因为内容太多了。我们希望用户自己去选择可离线访问的资源,保存到localstorage中。实现如下:

  1.首先以地址映射页面内容,地址映射标题(标题都保存到index指向的json字符串中)

  1. // Get the page content
  2. var pageHtml = document.body.innerHTML;
  3. var urlPath = location.pathName;
  4. // Save it in local storage
  5. localStorage.setItem( urlPath, pageHtml );
  6. // Get our index
  7. var index = JSON.parse( localStorage.getItem( 'index' ) );
  8. // Set the title and save it
  9. index[ urlPath ] = document.title;
  10. localStorage.setItem( 'index', JSON.stringify( index ) );

  2.通过清单指定一个fallback页面,页面中读取localstorage:

  1. var pageHtml = localStorage.getItem( urlPath );
  2. if ( !pageHtml ) {
  3. document.body.innerHTML = 'Page not available';
  4. }else {
  5. document.body.innerHTML = localStorage.getItem( urlPath );
  6. document.title = localStorage.getItem( 'index' )[ urlPath ];
  7. }

6.再见,条件下载

  对于响应式下载(媒体查询加载不同的图片,如source标签),浏览器会根据清单文件的内容来下载,而不管其他地方是否有媒体查询,这会导致浏览器下载多套不同的资源。同样的道理也会作用于字体

7.我们不知道什么时候会显示fallback页面

  根据说明(spec),当源请求被重定向到其他域、遇到4xx或5xx状态码、或网络错误时会显示fallback页面(显示的方式类似于服务器端跳转,即浏览器地址不变)。但是当用户没有网络的时候,fallback也没办法显示了。

8.重定向到其他域也会被认为是个错误

  如果页面访问一个url,服务器对这个url的请求进行重定向,这会马上显示一个对应url的fallback页面,因为appCache不允许。

  这个规则是好的,因为假设我们连了wifi上网,访问网站则重定向到了这个wifi的付费页面,相对于这点,显示fallback页面是好的。把这些需要重定向的url添加到NETWORK是无效的,但使用JS或者meta-redirect是可以的

其他阅读

离线web-ApplicationCache的更多相关文章

  1. HTML5离线Web应用实战:五步创建成功

    [IT168 技术]HTML5近十年来发展得如火如荼,在HTML 5平台上,视频,音频,图象,动画,以及同电脑的交互都被标准化.HTML功能越来越丰富,支持图片上传拖拽.支持localstorage. ...

  2. HTML5应用程序缓存实现离线Web网页或应用

    HTML5应用程序缓存和浏览器缓存的区别.(有些)浏览器会主动保存自己的缓存文件以加快网站加载速度.但是要实现浏览器缓存必须要满足一个前提,那就是网络必须要保持连接.如果网络没有连接,即使浏览器启用了 ...

  3. web离线应用--applicationCache

    applicationCache是html5新增的一个离线应用功能 离线浏览: 用户可以在离线状态下浏览网站内容. 更快的速度: 因为数据被存储在本地,所以速度会更快. 减轻服务器的负载: 浏览器只会 ...

  4. 文档通信(跨域-不跨域)、时时通信(websocket)、离线存储(applicationCache)、开启多线程(web worker)

    一.文档间的通信 postMessage对象 //不跨域 1.iframe:obj.contentWindow [iframe中的window对象] iframe拿到父级页面的window: pare ...

  5. 原生js--应用程序存储和离线web应用

    1.应用程序缓存和其它存储方式的区别: a.不像localStorage和sessionStorage那样只存储web应用程序的数据,它将应用程序自身存储起来. b.不像浏览器缓存一样会过期或者被用户 ...

  6. [HTML5]构建离线web应用程序

    1.检查浏览器是否支持缓存 if(window.applicationCache){ //TODO } 2.在html中加入manifest特性 <html manifest="app ...

  7. HTML5权威指南--Web Storage,本地数据库,本地缓存API,Web Sockets API,Geolocation API(简要学习笔记二)

    1.Web Storage HTML5除了Canvas元素之外,还有一个非常重要的功能那就是客户端本地保存数据的Web Storage功能. 以前都是用cookies保存用户名等简单信息.   但是c ...

  8. Html5离线应用程序

    最近,整理了一下关于 H5离线应用缓存的知识,今天在家休息,和大家分享一下,希望对大的学习和工作,能有所帮助. HTML5的离线web应用允许我们在脱机时与网站进行交互.这在提高网站的访问速度和制作一 ...

  9. HTML5实际和离线应用分析

    当前离线Web申请书,即,该装置不能访问因特网时的应用的执行.HTML5离线应用重点,主要开发人员希望.步骤离线应用开发有:首先我们应该知道设备是否可以连接;然后,它也应该可以访问某些资源(像.CSS ...

  10. HTML5离线应用与客户端存储

    序言 本篇文章会详细介绍使用HTML5开发离线应用的步骤,以及本地存储与cookie的一些异同,最后利用上面所学例子来实现一个购物车场景. 使用HTML5离线存储的基本过程如下: 离线检测:首先要对设 ...

随机推荐

  1. bzoj2825:[AHOI2012]收集资源

    传送门 看到数据范围这么小,就没想过暴力的办法么 考虑肯定是从近走到远,所以走的点之间一定没有其他的点,所以我们就可以暴力的建图,然后暴力的去dfs就好了 代码: #include<cstdio ...

  2. TopCoder9915(期望dp)

    1.还是逆向. 2.状态是还剩红i黑j张时的期望,这样从0,0往R,B推.注意因为是逆着的,所以到了某一步发现期望为负时直接f[i][j]归零,意义是这之后(在递推中算是这之前)的都不摸了,到这就停( ...

  3. SSE练习:单精度浮点数组求和

    SSE(Streaming SIMD Extensions)指令是一种SIMD 指令, Intrinsics函数则是对SSE指令的函数封装,利用C语言形式来调用SIMD指令集,大大提高了易读性和可维护 ...

  4. js AES对称加密 16进制和base64格式

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  5. phpmyadmin解决“高级功能尚未完全设置,部分功能未激活”

    首先在点击主页中的导入, 在“从计算机中上传:”选择/usr/share/doc/phpmyadmin/examples的“create_tables.sql.gz”文件 点击执行 但是我的电脑上还是 ...

  6. How to detect the presence of the Visual C++ 2010 redistributable package

    Question: I have seen your previous blog posts that describe how to detect the presence of the Visua ...

  7. Java编程基础-面向对象(下)

    一.抽象类 1.引入:当定义一个类时,常常需要定义一些方法来描述该类的行为特征,但有时这些方法的实现方式是无法确定的.Java允许在定义方法时不写方法体,不包含方法体的方法为抽象方法,抽象方法必须使用 ...

  8. CF1025C Plasticine zebra

    思路: 不要被骗了,这个操作实际上tm是在循环移位. 实现: #include <bits/stdc++.h> using namespace std; int main() { stri ...

  9. 浅析 var that = this;

    在阅读别人的代码时,发现别人写的代码中有这么一句:var that = this;,这代表什么意思呢?经过一番查阅,才明白是这么回事. 在JavaScript中,this代表的是当前对象. var t ...

  10. Android GreenDao 深查询 n:m 的关系

    在我的应用程序这样设计的关系:和我想选择至少一个用户作为一个朋友的所有聊天. 基本上,我想要执行以下查询:\ SELECT c.* FROM CHAT c, USER u, UserChats uc ...