浅谈web前端的发展趋势

8,842 阅读15分钟

前言

你一个写前端的,也敢自称程序员??

15308408976286

相信web前端开发的伙伴们,在职业道路上,十有八九会受到这样的质疑或者嘲讽(大多数其实还是调侃之意)。写几个标签,懂一些HTML CSS 就是程序员? 你们知道CPU、存储、网络、集群吗? 你们了解过并发、业务架构、数据库、性能调优、分布式计算、集群架构、容灾、安全、运维吗

哼 辣鸡👎

今日我们为前端带盐

近年来,Web 应用在整个软件与互联网行业承载的责任越来越重,软件复杂度和维护成本越来越高,Web 技术,尤其是 Web 客户端技术,迎来了爆发式的发展。

  • 1.用Node做中间层的前端工程化方案
  • 2.Webpack、Rollup 这样的打包工具;Babel、PostCSS 这样的转译工具
  • 3.前端三架马车React、Angular、Vue 这样面向现代 web 应用需求的前端框架及其生态
  • 4.与APP结合的混合开发模式,内嵌单页webview,Hybrid App

JavaScript 计算能力、CSS 布局能力、HTTP 缓存与浏览器 API带来了用户体验上质的飞跃

进入主题,我们将从2个方面:

  • 下一代Web应用:PWA
  • WebAssembly

来浅谈一下前端发展的趋势

下一代Web应用:PWA

老生常谈,我们先对比一下生活中WebAPP 和 原生APP的优劣

web APP 对比 原生APP 的优势
开发成本低
适配多种移动设备,不用IOS 安卓多套代码
迭代更新容易,省去了审核、发包、各种渠道发布带来的时间损耗
无需安装成本,拿来即用
web APP 对比 原生APP 的劣势
浏览的体验无法超越原生应用,加载慢,白屏转圈圈
很少有支持离线模式
消息推送及其困难
本地系统功能无法调用

PWA 的一系列关键技术的出现,终于让我们看到了彻底解决这两个平台级别问题的曙光

PWA解决的问题

  • 能够显著提高应用加载速度
  • 甚至让 web 应用可以在离线环境使用 (Service Worker)
  • web 应用能够像原生应用一样被添加到主屏、全屏执行 (Web App Manifest)
  • 进一步提高 web 应用与操作系统集成能力,让 web 应用能在未被激活时发起推送通知 (Push API 与 Notification API) 等等。

一个十分成熟的🌰(例子) 「印度阿里巴巴」 —— Flipkart

FlipKart Lite应该是最为人津津乐道的PWA案例了 当浏览器发现用户需要 Flipkart Lite 时,它就会提示用户“Hello,你可以把它添加至主屏哦”,当然也可以右上角手动添加。 这样,Flipkart Lite 就会像原生应用一样在主屏上留下一个自定义的 icon 作为入口;与一般的添加一个Web书签不同,当用户点击这个 icon 时,Flipkat Lite 将直接全屏打开,不再受困于浏览器的 UI 中,而且有自己的启动屏效果。

15300655325976

而且有一个很大的突破,在无法访问网络时,Flipkart Lite 可以像原生应用一样照常执行,还会很骚气的变成黑白色;不但如此,曾经访问过的商品都会被缓存下来得以在离线时继续访问。在商品降价、促销等时刻,Flipkart Lite 会像原生应用一样发起推送通知,吸引用户回到应用。

15300656314905

接下来我们看看PWA的2个重要技术点,Web APP Manifest 和 Service Worker

Web App Manifest

参考链接:https://developers.google.com/web/fundamentals/web-app-manifest/?hl=zh-cn

它其实是一个网络应用清单,一个JSON文件,开发者可以利用它控制在用户想要看到应用的区域(例如移动设备主屏幕)中如何向用户显示网络应用或网站,指示用户可以启动哪些功能,以及定义其在启动时的外观。是PWA技术的必备要素

总结一下Manifest的三个步骤:

  • 创建清单并将其链接到您的页面。
  • 控制用户从主屏幕启动时看到的内容。
  • 启动画面、主题颜色以及打开的网址等。

创建清单demo

15300662214848

short_name:为应用程序提供简短易读的名称。在没有足够空间显示全名时使用。 name:为应用程序提供一个人类可读的名称。 icons:各种环境中用作应用程序图标的图像对象数组 start_url:指定用户从设备启动应用程序时加载的URL。

15300662332376

在创建清单且将清单添加到您的网站之后,将 link 标记添加到包含网络应用的所有页面上

一些可选项

添加启动画面 splash screen

15300666570624

设置启动样式

15300666779957

这里是选择全屏显示,还是保留地址栏

debugger

15300675492998

Chrome 浏览器已经提供给我们一些方法和手段,直接进入 Application 板块,选择 manifest 选项卡,即可,将它添加到 Chrome 应用中。

html5里的manifest是用来缓存网页上的一些资源,跟我们PWA里的WebApp manifest 完全不是一回事

<!DOCTYPE HTML>
<html manifest="demo.appcache">
</html>

Service Worker

我们原有的整个 Web 应用,都是建立在用户能上网的前提之下的,所以一离线就只能看转圈圈了。web社区也做过很多类似的尝试,如APP Cache。但是它,几乎没有路由机制,出了BUG无法监控,现下已经在html5.1中 被干掉了

这个时候,Service workers 横空出世!!

Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API。

service worker将遵守以下生命周期:

  • 下载
  • 安装
  • 激活

15300681075578
15300681336107

看一下实例代码


if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {

    if(reg.installing) {
      console.log('Service worker installing');
    } else if(reg.waiting) {
      console.log('Service worker installed');
    } else if(reg.active) {
      console.log('Service worker active');
    }

  }).catch(function(error) {
    // registration failed
    console.log('Registration failed with ' + error);
  });
}

这段代码先做了一个特性检查,在注册之前确保 Service Worker 是支持的, 接着,我们使用 ServiceWorkerContainer.register() 函数来注册 service worker, 这就注册了一个 service worker。

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    })
  );
});
  • 新增了一个 install 事件监听器,接着在事件上接了一个ExtendableEvent.waitUntil() 方法——这会确保Service Worker 不会在 waitUntil() 里面的代码执行完毕之前安装完成。

  • 在 waitUntil()内,我们使用了 caches.open() 方法来创建了一个叫做 v1 的新的缓存,将会是我们的站点资源缓存的第一个版本。

  • 它返回了一个创建缓存的 promise,当它 resolved的时候,我们接着会调用在创建的缓存示例上的一个方法 addAll(),这个方法的参数是一个由一组相对于 origin 的 URL 组成的数组,这些 URL 就是你想缓存的资源的列表。

  • Service Worker 的 新的标志性的存储 API — cache — 一个 service worker 上的全局对象,它使我们可以存储网络响应发来的资源,并且根据它们的请求来生成key。

15300708550158

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers

推送Push Notification

Push API 的出现则让推送服务具备了向 web 应用推送消息的能力,它定义了 web 应用如何向推送服务发起订阅、如何响应推送消息,以及 web 应用、应用服务器与推送服务之间的鉴权与加密机制;由于 Push API 并不依赖 web 应用与浏览器 UI 存活,所以即使是在 web 应用与浏览器未被用户打开的时候,也可以通过后台进程接受推送消息并调用 Notification API 向用户发出通知


self.addEventListener('push', event => {
    event.waitUntil(
        // Process the event and display a notification.
        self.registration.showNotification("Hey!")
    );
});

self.addEventListener('notificationclick', event => {
    // Do something with the event
    event.notification.close();
});

self.addEventListener('notificationclose', event => {
    // Do something with the event
});

PWA任重道远

  • 国内较重视 iOS,而 iOS 对PWA是十分不友好的。

  • 国内的 Android 实为「安卓」,不自带 Chrome ,其次,各厂商喜欢自己瞎加班(JB)订制各种系统,带来兼容性问题

  • Push Notification还处于襁褓阶段(还没有一个标准的协议),未来的变数较大

  • 国内的web应用入口多集中于各类APP,如微信,qq,带来的限制较多

WebAssembly

部分图片和概念来自 参考链接:https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/

15300714659625
15300714727109

布兰登·艾克:你说你们老板10天上线1个app,丧心病狂?大哥10天干了一门语言

正是因为JS的诞生显得没有那么"正式",所以带来了很多的坑点和性能上的限制。它更像一个还在建造当中的楼房,我们web开发人员不断的为它添砖加瓦,总有一天会变成摩天大楼!

什么是WebAssembly

  • WebAssembly 是由主流浏览器厂商组成的 W3C 社区团体 制定的一个新的规范。

  • 它的缩写是".wasm",.wasm 为文件名后缀,是一种新的底层安全的二进制语法。

  • 可以接近原生的性能运行,并为诸如C / C ++等语言提供一个编译目标,以便它们可以在Web上运行。它也被设计为可以与JavaScript共存,允许两者一起工作。

  • 能突破前端3D game 、 VR/AR 、 机器视觉、图像处理等运行速度瓶颈

我们来看一个demo:http://webassembly.org.cn/demo/Tanks/

WebAssembly 工作原理

了解WebAssembly之前,我们先大概的了解一下代码的运行机制

在代码的世界中,通常有两种方式来翻译机器语言:解释器和编译器。

如果是通过解释器,翻译是一行行地边解释边执行

15300785911993

解释器启动和执行的更快。你不需要等待整个编译过程完成就可以运行你的代码。从第一行开始翻译,就可以依次继续执行了。

可是当你运行同样的代码一次以上的时候,解释器的弊处就显现出来了。比如你执行一个循环,那解释器就不得不一次又一次的进行翻译,这是一种效率低下的表现。

编译器是把源代码整个编译成目标代码,执行时不再需要编译器,直接在支持目标代码的平台上运行。

15300786239924

它需要花一些时间对整个源代码进行编译,然后生成目标文件才能在机器上执行。对于有循环的代码执行的很快,因为它不需要重复的去翻译每一次循环。

最开始的浏览器是只有解释器的,因为解释器看起来更加适合 JavaScript。对于一个 Web 开发人员来讲,能够快速执行代码并看到结果是非常重要的。后来将编译器也加入进来,形成混合模式。

再添加一个监视器,用来监控着代码的运行情况,记录代码一共运行了多少次、如何运行的等信息。

加这么多东西的好处是什么呢

起初,监视器监视着所有通过解释器的代码。

15300791434221

如果同一行代码运行了几次,这个代码段就被标记成了 “warm”,如果运行了很多次,则被标记成 “hot”。

如果一段代码变成了 “warm”,那么 浏览器 就把它送到编译器去编译,并且把编译结果存储起来。--(基线编译器)

代码段的每一行都会被编译成一个“桩”(stub),同时给这个桩分配一个以“行号 + 变量类型”的索引。如果监视器监视到了执行同样的代码和同样的变量类型,那么就直接把这个已编译的版本 push 出来给浏览器。

15300797943329

如果一个代码段变得 “very hot”,监视器会把它发送到优化编译器中。生成一个更快速和高效的代码版本出来,并且存储之。--(优化编译器)

优化编译器会做一些假设。如果某个循环中先前每次迭代的对象都有相同的形状,那么优化编译器就可以认为它以后迭代的对象的形状都是相同的。可是对于 JavaScript 从来就没有保证这么一说,前 99 个对象保持着形状,可能第 100 个就少了某个属性,这个时候,执行过程将会回到解释器或者基线编译器,叫做去优化

15300797943329

function arraySum(arr) {
  var sum = 0;
  for (var i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
}

如果arr 是一个有 100 个整数的数组,类型确定,就很容易的派发到优化编译器中 但是JavaScript 中类型都是动态类型,sum 和 arr[i] 两个数并不保证都是整数,arr[i] 很有可能变成了string 类型,就会去优化,重新分配到解释器或者基线编译器

那是如何编译解释呢?

我们进行机器码的翻译并不是只有一种,不同的机器有不同的机器码,就像我们人类也说各种各样的语言一样,机器也“说”不同的语言。

你想要从任意一个高级语言翻译到众多汇编语言中的一种(依赖机器内部结构),其中一种方式是创建不同的翻译器来完成各种高级语言到汇编的映射。

15300804906133

这种翻译的效率实在太低了。为了解决这个问题,大多数编译器都会在中间多加一层。它会把高级语言翻译到一个低层,而这个低层又没有低到机器码这个层级。这就是中间代码( intermediate representation,IR)。

15300807406283

编译器的前端把高级语言翻译到 IR,编译器的后端把 IR 翻译成目标机器的汇编代码。

重点来了

15300808312348

WebAssembly 在什么位置呢?实际上,你可以把它看成另一种“目标汇编语言”。

每一种目标汇编语言(x86、ARM)都依赖于特定的机器结构。当你想要把你的代码放到用户的机器上执行的时候,你并不知道目标机器结构是什么样的。

而 WebAssembly 与其他的汇编语言不一样,它不依赖于具体的物理机器。可以抽象地理解成它是概念机器的机器语言,而不是实际的物理机器的机器语言。

正因为如此,WebAssembly 指令有时也被称为虚拟指令。它比 JavaScript 代码更直接地映射到机器码,它也代表了“如何能在通用的硬件上更有效地执行代码”的一种理念。所以它并不直接映射成特定硬件的机器码。

那为什么不直接用JS,这么麻烦用WebAssembly

15300826048069

这是JS的性能使用分布情况

  • Parsing——表示把源代码变成解释器可以运行的代码所花的时间;

  • Compiling + optimizing——表示基线编译器和优化编译器花的时间。一些优化编译器的工作并不在主线程运行,不包含在这里。

  • Re-optimizing——包括重优化的时间、抛弃并返回到基线编译器的时间。

  • Execution——执行代码的时间

  • Garbage collection——垃圾回收,清理内存的时间

15300828887458

这是WebAssmbly与JS的对比

wasm的优势是本身就是通过编译器并优化过后的二进制文件,可以直接转换为机器码,省去了Javascript需要解析,优化的工作,所以在加载和执行上本身就具有优势

具体优势点

  • 文件获取

WebAssembly 比 JavaScript 的压缩率更高,所以文件获取也更快。即便通过压缩算法可以显著地减小 JavaScript 的包大小,但是压缩后的 WebAssembly 的二进制代码依然更小。

这就是说在服务器和客户端之间传输文件更快,尤其在网络不好的情况下。

  • 解析

JavaScript 源代码到达浏览器时被解析成了AST (抽象语法树)。 解析过后 AST (抽象语法树)就变成了中间代码(叫做字节码),提供给 JS 引擎编译。

而 WebAssembly 则不需要这种转换,因为它本身就是中间代码。它要做的只是解码并且检查确认代码没有错误就可以了。

  • 优化

浏览器的JIT会反复地进行“抛弃优化代码<->重优化”过程, 比如当循环中发现本次循环所使用的变量类型和上次循环的类型不一样,或者原型链中插入了新的函数,都会使 JIT 抛弃已优化的代码,进行重优化。

在 WebAssembly 中,类型都是确定了的,所以 JIT 不需要根据变量的类型做优化假设。也就是说 WebAssembly 没有重优化阶段。

  • 垃圾回收

在JS中的内存概念是非常模糊的,因为JS并不需要申请内存,所有内存都有JS自动分配,因为它不可控,所以清理垃圾的时候会带来性能开销

WebAssembly不需要垃圾回收,内存操作都是手动控制的(像 C、C++一样)。这对于开发者来讲确实增加了些开发成本,不过这也使代码的执行效率更高。

如何实现一个WebAssembly demo

参考链接:http://webassembly.org.cn/getting-started/developers-guide/

15300836901005

int square (int x) {
  return x * x;
}
emcc math.c -s WASM=1 -o index.html

15300837997256

从0开始完成刚刚坦克大战的例子

15300845024215

15300845134273

4.大功告成

谢谢大家~

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~