灵活的脚本语言,能方便的处理业务逻辑

  JavaScript 是个灵活的脚本语言,能方便的处理业务逻辑。当需要传输通信时,我们大多选择 JSON 或 XML 格式。

  但在数据长度非常苛刻的情况下,文本协议的效率就非常低了js数组应用,这时不得不使用二进制格式。

  去年的今天,在折腾一个 前后端结合的 WAF 时,就遇到了这个麻烦。

  因为前端脚本需要采集不少数据,而最终是隐写在某个 cookie 里的,因此可用的长度非常有限,只有几十个字节。

  如果不假思索就用 JSON 的话,光一个标记字段 {"enableXX": true} 就占去了一半长度。然而在二进制里,标记 true 或 false 不过是 1 个比特的事,可以节省上百倍的空间。

  同时,数据还要经过校验、加密等环节,只有使用二进制格式,才能方便的调用这些算法。

  优雅实现

  不过js数组应用,JavaScript 并不支持二进制。

  这里的「不支持」不是说「无法实现」,而是无法「优雅实现」。语言的发明,就是用来优雅解决问题的。即使没有语言,人类也可以用机器指令来编写程序。

  如果非要用 JavaScript 操作二进制,最终就类似这样:

  var flags = +enableXX1

  虽然能实现,但很丑陋。各种硬编码、各种位运算。

  然而,对于先天支持二进制的语言,看起来就十分优雅:

  union{

  struct{

  intenableXX1: 1;

  intenableXX2: 1;

  ...

  };

  int16_t value;

  }flags;

  flags.enableXX1 = enableXX1;

  flags.enableXX2 = enableXX2;

  开发者只需定义一个描述即可。使用时,字段偏移多少、如何读写,这些细节完全不用关心。

  为了能达到类似效果,起先封装了一个 JS 版的结构体:

  // 最初方案:封装一个 JS 结构体

  vars = newStruct([

  {name: 'month',bit: 4,signed: false},

  ...

  ]);

  s.set('month',12);

  s.get('month');

  将细节进行了隐藏,看起来就优雅多了。

  优雅但不完美

  但是,这总感觉不是最完美的。结构体这种东西,本该由语言提供,如今却要用额外的代码实现,而且还是在运行期间。

  另外,后端解码是用 C 实现的,所以得维护两套代码。一旦数据结构或者算法变了,得同时更新 JS 和 C,很麻烦。

  于是琢磨,能否共用一套 C 代码,同时用于前端和后端?

  也就是说,需要能将 C 编译成 JS 来运行。

  认识 emscripten

  能将 C 编译成 JS 的工具有不少,最专业的要数 emscripten。

  emscripten 的使用方式很简单,和传统 C 编译器差不多,只不过生成的是 JS 代码。

  emcc hello.c -ohello.html

  // hello.c

  #include

  #include

  intmain(){

  time_t now;

  time(&now);

  printf("Hello World: %s",ctime(&now));

  return0;

  }

  编译之后即可运行:

  js数组添加数组_js数组应用_js 数组字符串 转数组

  很有趣吧~ 大家可以尝试下,这里就不多介绍了。

  实用缺陷

  然而我们关心的不是有趣,而是实用。

  事实上,即使一个 Hello World 编译出来的 JS 也过万行,多达数百 KB。就算压缩再 GZIP,仍有几十 KB。

  同时 emscripten 使用了 asm.js 规范,内存访问是通过 TypedArray 实现的。

  这意味着 IE10 以下的用户都无法运行。这也是不可接受的。

  因此,我们得做如下改进:

  首先寄托 emscripten 本身,看看能不能通过设置参数,来达到我们的目的。

  不过一番尝试之后,并没有成功。那只能自己动手实现了。

  减少体积

  为什么最终脚本会那么大,里面都放了些什么?分析了下内容,大致有这几个部分:

  辅助功能

  比如字符串和二进制转换、提供回调包装等。这些基本都是用不着的,我们可以给自己写个特殊的回调函数。

  接口模拟

  提供文件、终端、网络、渲染等接口。之前见过用 emscripten 移植的客户端游戏,看来模拟了不少接口。

  初始化操作

  全局内存、运行时、各种模块的初始化。

  运行时函数

  纯粹的 C 只能做简单的计算,很多功能都依靠运行时函数。

  不过,有些常用的函数,其背后的实现是及其复杂的。例如 malloc 和 free,对应的 JS 有近 2000 行!

  程序逻辑

  这才是 C 程序真正对应的 JS 代码。因为编译时经过 LLVM 的优化,逻辑可能变得面目全非了。

  这部分代码量不大,是我们真正想要的。

  事实上,如果程序没有用到一些特殊功能的话,把逻辑函数单独抠出来,仍然是可以运行的!

  考虑到我们的 C 程序非常简单,所以简单粗暴的提取出来,也是没问题的。

  C 程序对应的 JS 逻辑位于 // EMSCRIPTEN_START_FUNCS 和 // EMSCRIPTEN_END_FUNCS 之间。过滤掉运行时函数,剩下的就是 100% 的逻辑代码了。

  增加兼容

  接着解决内存访问的兼容性问题。

  在很老版本的 emscripten 里,是可以选择是否使用 TypedArray 的。如果不用,则通过 JS Array 来实现。但如今早已去除了这个参数,只能使用 TypedArray。

  首先了解下,为何要用 TypedArray。

  emscripten 申请了一大块 ArrayBuffer 来模拟内存,然后关联了一些 HEAP 开头的变量。

  js数组应用_js数组添加数组_js 数组字符串 转数组

  这些不同类型的 HEAP 共享同一块内存,这样就能高效的指针操作。

  然而不支持 TypedArray 的浏览器,显然无法运行。所以得提供个 polyfill 兼容下。

  但经分析,这几乎不可能实现 —— 因为 TypedArray 和数组一样,是通过索引来访问的:

  varbuf = newUint8Array(100);

  buf[0] = 123; // set

  alert(buf[0]);// get

  然而 [] 操作符在 JS 里是无法重写的,因此难以将其变成 setter 和 getter。况且不支持 TypedArray 的都是低版本 IE,更不用考虑 ES6 的那些特征。

  于是琢磨 IE 的私有接口。比如用 onpropertychange 事件来模拟 setter。不过这样做效率极低,而且 getter 仍不易实现。

  经过一番考虑,决定不用钩子的方式,而是直接从源头上解决 —— 修改语法!

  我们用正则,找出源码中的赋值操作:

  HEAP[index] = val;

  替换成:

  HEAP_SET(index, val);

  类似的,将读取操作:

  HEAP[index]

  替换成:

  HEAP_GET(index)

  这样,原先的索引操作,就变成函数调用了。我们就能接管内存的读写,并且没有任何兼容性问题!

  然后实现 8、16、32 位有无符号的版本。通过 JS 的 Array 来模拟,非常简单。

  js数组添加数组_js 数组字符串 转数组_js数组应用

  麻烦的是模拟 Float32 和 Float64 两个类型。不过本次 C 程序中并未用到浮点,所以就暂不实现了。

  到此,兼容性问题就解决了。

  大功告成

  解决了这些缺陷,我们就可以愉快的在 JS 中使用 C 逻辑了。

  作为脚本,只需关心采集哪些数据,这样代码就非常的优雅:

  js数组应用_js 数组字符串 转数组_js数组添加数组

  数据的储存、加密、编码,这些二进制操作,则通过 C 实现。

  js 数组字符串 转数组_js数组添加数组_js数组应用

  编译时使用 -Os 参数优化体积,最终的 JS 精简压缩之后,还不到 2 KB,十分小巧精炼。

  js数组添加数组_js数组应用_js 数组字符串 转数组

  于是,这个「前后端 WAF」开发就容易多了。我们只需维护一份代码,即可同时编译出前后端两个版本!

  所有的数据结构和算法,都由 C 实现。前端编译成 JS 代码,后端编译成 lua 模块,供 nginx-lua 使用。

  js 数组字符串 转数组_js数组应用_js数组添加数组

  前后端的脚本,都只需关注业务功能即可,完全不用涉及数据层面的细节。

  测试版

  事实上,还有第三个版本 —— 本地版。

  因为所有的 C 代码都在一起,因此可以方便的编写测试程序。

  这样就无需启动 WebServer、打开浏览器来测试了。只需模拟一些数据,直接运行程序即可测试,非常轻量。

  同时借助 IDE,调试起来更容易。

  小结

  每一门语言都有各自的优缺点。将不同语言的优势相互结合,可以让程序变得更优雅、更完美。

  本文属于「前端大全」Javascript 分类。

文章由官网发布,如若转载,请注明出处:https://www.veimoz.com/1517
0 评论
527

发表评论

!