@Wahson
2017-03-24T10:27:16.000000Z
字数 4415
阅读 1835
前端技术
Native App:原生的应用,直接使用Object C(ios)或者Java(android)开发的App。
Web App:即一般的网页应用,在浏览器中打开。
Hybrid App:半原生半Web的混合类App,普遍采用的是Native的框架,Web的内容。如快塑网App,由Cordova提供不同系统的native框架,并向页面暴露访问设备的接口,使用Polymer等前端技术编写UI。
| Native App | Web App | PWA | |
|---|---|---|---|
| 操作体验 | ✔️好 | ✘ 较差 | ✔️ |
| 性能 | ✔️稳定 | ✘受限于浏览器 | ✔️ |
| 访问本地资源(通讯录、相册等) | ✔️ | ✘ | ✔️ |
| 动效,转场 | ✔️ 动画效果出色 | ✘ | ✔️ |
| 消息推送 | ✔️ | ✘ | ✔️ |
| 开发成本 | ✘高 | ✔️相对较低 | ✔️ |
| 更新发布 | ✘缓慢,根据不同平台,提交-审核-上线流程复杂 | ✔升级于无形 | ✔️ |
| 跨平台 | ✘根据不同平台开发多个版本 | ✔️能够跨多个平台和终端 | ✔️ |
针对Web App和NativeApp的优缺点,Hybrid App就是一个折中的解决办法。
PWA本质上仍然是一个Web App,它依赖了更多ES6已标准化或起草阶段的API。
它的特点:
- 渐进增强:能够让每一位用户使用,无论用户使用什么浏览器,因为它是始终以渐进增强为原则。
- 响应式用户界面:适应任何环境:桌面电脑,智能手机,笔记本电脑,或者其他设备。
- 不依赖网络连接:通过 service workers 可以在离线或者网速极差的环境下工作。
- 类原生应用:有像原生应用般的交互和导航给用户原生应用般的体验,因为它是建立在 app shell model 上的。
- 持续更新:受益于 service worker 的更新进程,应用能够始终保持更新。
- 安全:通过 HTTPS 来提供服务来防止网络窥探,保证内容不被篡改。
- 可发现:得益于 W3C manifests 元数据和 service worker 的登记,让搜索引擎能够找到 web 应用。
- 再次访问: - 通过消息推送等特性让用户再次访问变得容易。
- 可安装: - 允许用户保留对他们有用的应用在主屏幕上,不需要通过应用商店。
- 可连接性:通过 URL 可以轻松分享应用,不用复杂的安装即可运行。
PWA的核心是ServiceWorker。
ServiceWorker的官方解释:服务工作线程是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。现在,它们已包括如推送通知和后台同步等功能。将来,服务工作线程将会支持如定期同步或地理围栏等其他功能。
- 它是一种 JavaScript 工作线程,无法直接访问 DOM。 服务工作线程通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
- 服务工作线程是一种可编程网络代理,让您能够控制页面所发送网络请求的处理方式。
- 它在不用时会被中止,并在下次有需要时重启,因此,您不能依赖于服务工作线程的 onfetch 和 onmessage 处理程序中的全局状态。如果存在您需要持续保存并在重启后加以重用的信息,服务工作线程可以访问 IndexedDB API。
- 服务工作线程广泛地利用了 promise。
要实现离线工作,无非就是把资源缓存在本地,当网络环境不好的时候,直接从本地获取资源。PWA的离线能力依赖于:
- 在Service workers 生命周期的不同阶段,可以根据资源的类型来缓存数据。
- Service workers 可以截获 Progressive Web App 发起的请求并从缓存中返回响应。
在serviceWorker安装成功后,install事件会被触发,此时可以把静态的UI缓存起来。

/*** service-worker.js* 存储应用外壳需要的资源*/const cacheName = 'weatherPWA-step-6-1';//资源列表const filesToCache = ['/','/index.html','/scripts/app.js','/styles/inline.css','/images/clear.png','/images/cloudy-scattered-showers.png','/images/cloudy.png',//...];self.addEventListener('install', e => {console.log('[ServiceWorker] Install');e.waitUntil(caches.open(cacheName).then(cache => {console.log('[ServiceWorker] Caching app shell');return cache.addAll(filesToCache);}));});
serviceWorker拦截请求,根据请求匹配缓存中的响应,如果找到了则直接返回,否则将发起网络请求,然后把响应的结果返回,并且添加到缓存中以备下次使用。

/*** service-worker.js* 在页面发起http请求时,serviceWorker可以通过fetch事件拦截请求,并且给出自己的响应。** 当一个请求被拦截时,首先会检查缓存里是否有上次发生的相同请求的响应,如果有则直接返回,否则重新发起请求,并把响应缓存起来供下次使用。* 当然,这样做并不合理,因为除了第一次外,每次请求都是取到缓存的数据,但是缓存可能早已过期。*/const dataCacheName = 'weatherData-v1';self.addEventListener('fetch', e => {e.respondWith(caches.match(e.request).then(response => {return response || fetch(e.request).then(response => {return caches.open(dataCacheName).then(cache => {cache.put(e.request.url, response.clone());return response;});}));}});
另外要解决一个问题:缓存过期时如何处理?PWA需要编程式的告诉serviceWorker需要删除哪些缓存。当serviceWorker被激活后,可以判断哪些缓存已经过期了(如何判断取决于你),然后把那些过期的移除。
要注意,当有文件被修改后,都需要修改
cacheName,修改后,整个缓存都需要重新下载。这显然不是很高效。

const cacheName = 'weatherPWA-step-6-2';self.addEventListener('activate', function(event) {event.waitUntil(caches.keys().then(function(cacheNames) {return Promise.all(cacheNames.filter(function(cacheName) {// Return true if you want to remove this cache,// but remember that caches are shared across// the whole origin}).map(function(cacheName) {return caches.delete(cacheName);}));}));});
- 下载应用,应用安装服务工作线程。用户授权允许消息推送服务。
- App发起消息订阅。
- 在订阅流期间,浏览器联系消息服务器来创建新的订阅并将其返回应用。
注:您不需要知道消息服务器的网址。每个浏览器厂商都为旗下浏览器管理自有的消息服务器。- 在订阅流后,您的应用会将订阅对象传回您的应用服务器。
- 在稍后的某个时间,您的应用服务器会向消息服务器发送一条消息,后者会将其转发至接收方。
/*** 注册service worker*/if('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js') //service-worker.js包含允许在后台运行的脚本.then(swRegistration => {//swRegistration后续需要用来订阅/退订消息// Registration was successful}).catch(err => {// registration failed :(console.log('ServiceWorker registration failed: ', err);});}
利用service worker订阅和退订消息
/*** 订阅消息*/swRegistration.pushManager.subscribe({userVisibleOnly: true,applicationServerKey: applicationServerKey}).then(subscription => {//User is subscribedupdateSubscriptionOnServer(subscription);}).catch(err => {//Failed to subscribe the user});
/*** 退订消息*/swRegistration.pushManager.getSubscription().then(subscription => {if (subscription) {return subscription.unsubscribe();}}).catch(error => {console.log('Error unsubscribing', error);}).then(() => {//remove subscription on server);
PWA目前还难以应用起来,看看PWA的核心技术在各大浏览器中的支持情况:
SupportedNot supportedPartial support