你一个写前端的,也敢自称程序员??
相信web前端开发的伙伴们,在职业道路上,十有八九会受到这样的质疑或者嘲讽(大多数其实还是调侃之意)。写几个标签,懂一些HTML CSS 就是程序员? 你们知道CPU、存储、网络、集群吗? 你们了解过并发、业务架构、数据库、性能调优、分布式计算、集群架构、容灾、安全、运维吗
哼 辣鸡?
近年来,Web 应用在整个软件与互联网行业承载的责任越来越重,软件复杂度和维护成本越来越高,Web 技术,尤其是 Web 客户端技术,迎来了爆发式的发展。
JavaScript 计算能力、CSS 布局能力、HTTP 缓存与浏览器 API带来了用户体验上质的飞跃
进入主题,我们将从2个方面:
来浅谈一下前端发展的趋势
老生常谈,我们先对比一下生活中WebAPP 和 原生APP的优劣
web APP 对比 原生APP 的优势 |
---|
开发成本低 |
适配多种移动设备,不用IOS 安卓多套代码 |
迭代更新容易,省去了审核、发包、各种渠道发布带来的时间损耗 |
无需安装成本,拿来即用 |
web APP 对比 原生APP 的劣势 |
---|
浏览的体验无法超越原生应用,加载慢,白屏转圈圈 |
很少有支持离线模式 |
消息推送及其困难 |
本地系统功能无法调用 |
PWA 的一系列关键技术的出现,终于让我们看到了彻底解决这两个平台级别问题的曙光
FlipKart Lite应该是最为人津津乐道的PWA案例了 当浏览器发现用户需要 Flipkart Lite 时,它就会提示用户“Hello,你可以把它添加至主屏哦”,当然也可以右上角手动添加。 这样,Flipkart Lite 就会像原生应用一样在主屏上留下一个自定义的 icon 作为入口;与一般的添加一个Web书签不同,当用户点击这个 icon 时,Flipkat Lite 将直接全屏打开,不再受困于浏览器的 UI 中,而且有自己的启动屏效果。
而且有一个很大的突破,在无法访问网络时,Flipkart Lite 可以像原生应用一样照常执行,还会很骚气的变成黑白色;不但如此,曾经访问过的商品都会被缓存下来得以在离线时继续访问。在商品降价、促销等时刻,Flipkart Lite 会像原生应用一样发起推送通知,吸引用户回到应用。
接下来我们看看PWA的2个重要技术点,Web APP Manifest 和 Service Worker
参考链接:https://developers.google.com/web/fundamentals/web-app-manifest/?hl=zh-cn
它其实是一个网络应用清单,一个JSON文件,开发者可以利用它控制在用户想要看到应用的区域(例如移动设备主屏幕)中如何向用户显示网络应用或网站,指示用户可以启动哪些功能,以及定义其在启动时的外观。是PWA技术的必备要素
总结一下Manifest的三个步骤:
short_name:为应用程序提供简短易读的名称。在没有足够空间显示全名时使用。 name:为应用程序提供一个人类可读的名称。 icons:各种环境中用作应用程序图标的图像对象数组 start_url:指定用户从设备启动应用程序时加载的URL。
在创建清单且将清单添加到您的网站之后,将 link 标记添加到包含网络应用的所有页面上
这里是选择全屏显示,还是保留地址栏
Chrome 浏览器已经提供给我们一些方法和手段,直接进入 Application 板块,选择 manifest 选项卡,即可,将它添加到 Chrome 应用中。
html5里的manifest是用来缓存网页上的一些资源,跟我们PWA里的WebApp manifest 完全不是一回事
<!DOCTYPE HTML>
<html manifest="demo.appcache">
</html>
复制代码
我们原有的整个 Web 应用,都是建立在用户能上网的前提之下的,所以一离线就只能看转圈圈了。web社区也做过很多类似的尝试,如APP Cache。但是它,几乎没有路由机制,出了BUG无法监控,现下已经在html5.1中 被干掉了
这个时候,Service workers 横空出世!!
Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API。
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'
]);
})
);
});
复制代码
参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers
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
});
复制代码
部分图片和概念来自 参考链接:https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/
布兰登·艾克:你说你们老板10天上线1个app,丧心病狂?大哥10天干了一门语言
正是因为JS的诞生显得没有那么"正式",所以带来了很多的坑点和性能上的限制。它更像一个还在建造当中的楼房,我们web开发人员不断的为它添砖加瓦,总有一天会变成摩天大楼!
我们来看一个demo:http://webassembly.org.cn/demo/Tanks/
了解WebAssembly之前,我们先大概的了解一下代码的运行机制
在代码的世界中,通常有两种方式来翻译机器语言:解释器和编译器。
如果是通过解释器,翻译是一行行地边解释边执行
解释器启动和执行的更快。你不需要等待整个编译过程完成就可以运行你的代码。从第一行开始翻译,就可以依次继续执行了。 可是当你运行同样的代码一次以上的时候,解释器的弊处就显现出来了。比如你执行一个循环,那解释器就不得不一次又一次的进行翻译,这是一种效率低下的表现。
编译器是把源代码整个编译成目标代码,执行时不再需要编译器,直接在支持目标代码的平台上运行。
它需要花一些时间对整个源代码进行编译,然后生成目标文件才能在机器上执行。对于有循环的代码执行的很快,因为它不需要重复的去翻译每一次循环。
最开始的浏览器是只有解释器的,因为解释器看起来更加适合 JavaScript。对于一个 Web 开发人员来讲,能够快速执行代码并看到结果是非常重要的。后来将编译器也加入进来,形成混合模式。
再添加一个监视器,用来监控着代码的运行情况,记录代码一共运行了多少次、如何运行的等信息。
起初,监视器监视着所有通过解释器的代码。
如果同一行代码运行了几次,这个代码段就被标记成了 “warm”,如果运行了很多次,则被标记成 “hot”。 如果一段代码变成了 “warm”,那么 浏览器 就把它送到编译器去编译,并且把编译结果存储起来。--(基线编译器) 代码段的每一行都会被编译成一个“桩”(stub),同时给这个桩分配一个以“行号 + 变量类型”的索引。如果监视器监视到了执行同样的代码和同样的变量类型,那么就直接把这个已编译的版本 push 出来给浏览器。
如果一个代码段变得 “very hot”,监视器会把它发送到优化编译器中。生成一个更快速和高效的代码版本出来,并且存储之。--(优化编译器) 优化编译器会做一些假设。如果某个循环中先前每次迭代的对象都有相同的形状,那么优化编译器就可以认为它以后迭代的对象的形状都是相同的。可是对于 JavaScript 从来就没有保证这么一说,前 99 个对象保持着形状,可能第 100 个就少了某个属性,这个时候,执行过程将会回到解释器或者基线编译器,叫做去优化
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 类型,就会去优化,重新分配到解释器或者基线编译器
我们进行机器码的翻译并不是只有一种,不同的机器有不同的机器码,就像我们人类也说各种各样的语言一样,机器也“说”不同的语言。 你想要从任意一个高级语言翻译到众多汇编语言中的一种(依赖机器内部结构),其中一种方式是创建不同的翻译器来完成各种高级语言到汇编的映射。
这种翻译的效率实在太低了。为了解决这个问题,大多数编译器都会在中间多加一层。它会把高级语言翻译到一个低层,而这个低层又没有低到机器码这个层级。这就是中间代码( intermediate representation,IR)。
编译器的前端把高级语言翻译到 IR,编译器的后端把 IR 翻译成目标机器的汇编代码。
重点来了
WebAssembly 在什么位置呢?实际上,你可以把它看成另一种“目标汇编语言”。 每一种目标汇编语言(x86、ARM)都依赖于特定的机器结构。当你想要把你的代码放到用户的机器上执行的时候,你并不知道目标机器结构是什么样的。 而 WebAssembly 与其他的汇编语言不一样,它不依赖于具体的物理机器。可以抽象地理解成它是概念机器的机器语言,而不是实际的物理机器的机器语言。 正因为如此,WebAssembly 指令有时也被称为虚拟指令。它比 JavaScript 代码更直接地映射到机器码,它也代表了“如何能在通用的硬件上更有效地执行代码”的一种理念。所以它并不直接映射成特定硬件的机器码。
这是JS的性能使用分布情况
这是WebAssmbly与JS的对比
wasm的优势是本身就是通过编译器并优化过后的二进制文件,可以直接转换为机器码,省去了Javascript需要解析,优化的工作,所以在加载和执行上本身就具有优势
WebAssembly 比 JavaScript 的压缩率更高,所以文件获取也更快。即便通过压缩算法可以显著地减小 JavaScript 的包大小,但是压缩后的 WebAssembly 的二进制代码依然更小。
这就是说在服务器和客户端之间传输文件更快,尤其在网络不好的情况下。
JavaScript 源代码到达浏览器时被解析成了AST (抽象语法树)。 解析过后 AST (抽象语法树)就变成了中间代码(叫做字节码),提供给 JS 引擎编译。
而 WebAssembly 则不需要这种转换,因为它本身就是中间代码。它要做的只是解码并且检查确认代码没有错误就可以了。
浏览器的JIT会反复地进行“抛弃优化代码<->重优化”过程, 比如当循环中发现本次循环所使用的变量类型和上次循环的类型不一样,或者原型链中插入了新的函数,都会使 JIT 抛弃已优化的代码,进行重优化。
在 WebAssembly 中,类型都是确定了的,所以 JIT 不需要根据变量的类型做优化假设。也就是说 WebAssembly 没有重优化阶段。
在JS中的内存概念是非常模糊的,因为JS并不需要申请内存,所有内存都有JS自动分配,因为它不可控,所以清理垃圾的时候会带来性能开销
WebAssembly不需要垃圾回收,内存操作都是手动控制的(像 C、C++一样)。这对于开发者来讲确实增加了些开发成本,不过这也使代码的执行效率更高。
参考链接:http://webassembly.org.cn/getting-started/developers-guide/
int square (int x) {
return x * x;
}
复制代码
emcc math.c -s WASM=1 -o index.html
复制代码
4.大功告成
谢谢大家~
本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。