javascript自从被创造开始就吐槽不断,它确实也埋下了不少的坑。
首先,它是一种解释性语言,大神最开始的设计目标用户就是“非专业编程人员和设计师”,避免了非专业人士对编译器了解的需要,解释性语言就是边解释边执行,与编译性语言的先编译后执行相比,执行速度慢了很多;
其次,javascript中没有类型,因为学习类型就需要学习cpu啦。有类型的语言在编译生成本地代码的过程中,就已经确定了其变量地址和类型,运行本地代码时通过数组和位移就可以存取变量和方法,不需要额外的查找,但是无类型语言就需要临时确定,每次执行需要重新确定变量存储栈区的变量标志符、变量值或地址、堆区存储的对象;
再次,对象模型也比较奇葩,没有泛型、缺省参数这些。
这些直接导致的就是性能问题,于是就是开始了漫长的填坑过程。
v8引擎的JIT,在代码执行的前一刻,引擎会编译需要运行的代码,v8更加直接的将抽象语法树通过JIT 技术转换成本地代码,由此保证了执行速度。
但是!JIT依然跨不过无类型语言的坑,煮个栗子:
function add(a, b) {
return a + b;
}
var c = add( 1 + 2);
这时,JIT会编译为
function add(int a, int b) {
return a + b;
}
但是若遇到以下情况JIT就只能重新编译一遍
var d = add('1' + '2');
为了解决无类型语言的障碍,我们可以把变量类型标注出来就好啦!于是就有了我们常用的TypeScript和JSX(强类型语言),最后再编译成弱类型语言,但保证了同一变量或方法的类型不会变来变去。
另外一个比较火的是火狐的asm.js,利用 | & << >>等符号来标志变量的类型,这样编译器就不需要猜类型了。
asm是mozilla提出的一套基于JS的语法标准,所以它是javascript的一个子集。主要是由Emscrpiten项目催生出来的,目的是解决js的执行效率问题。
但是实际上asm.js只能处理几种数值类型,对于字符串和布尔型变量没有做处理。JS语言不仅是弱类型的,而且数值类型只有一种-Number,Number类型的数据采用双精度64位格式的IEEE 754值表示.我们从代码角度看下asm干了些什么:
// c程序:
char xInt8 = 127;
char yInt8 = xInt8 + 1; // 溢出:yInt8 == (char) -128
char zInt8 = xInt8 / 2; // 舍入:zInt8 == (char) 63
上面这段代码通过JS模拟后的代码如下:
var xInt8 = 127; // (1) var $add = (xInt8 + 1) | 0; // (2) var yInt8 = ($add << 24) >> 24; // (3) var $div = ((xInt8 | 0) / 2) & -1; // (4) var zInt8 = ($div << 24) >> 24; // (5)
(2)“| 0”告诉js引擎这里的标识符是个integer,它可以帮助JIT编译器生成integer相应的机器代码。这里也是asm的一个关键点:类型注释。
(3)先左移24位再右移24位,让第8位成为32位整数的符号位,来模拟8位整数计算,此时yInt8 == -128
(4)js中127/2结果是浮点数63.5,利用X &-1将结果转化成32位整数63
补充知识点:
<< 左移,>> 有符号右移, >>> 无符号右移,二进制位运算符(<<, >>, >>>, |, ^, ~, &, !)等都是将int -> int,即将操作数识别为integer。
于是,利用一些位移和逻辑运算可以模拟C/C++语言中的数据计算,Emscripten就利用这个方法将C代码转换成JS代码。另外一种更为简单的方法是,对Typed Array元素赋值则会自动进行相应的溢出和舍入处理。
它是H5标准与性能之一,主要是为了弥补js处理二进制格式数据的不足,利用Typed Array可以非常方便地操作二进制的数据(例如二进制的文件、网络数据等等),固定类型数值的计算加速,或者实现类似C的struct和union的功能。
Typed Array主要由下面几个类构成:
ArrayBuffer: 连续的内存缓冲区,用于实际储存各种类型的数组数据
Typed Array View类:比如Int32Array、Uint8Array、Float32Array等,表示一个特定类型的数组
DataView: 工具类,提供getUint8、setFloat32等工具方法修改ArrayBuffer不同位置的数据值
//浮点型数组
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);
//有符号整型数组
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);
//无符号整型数组
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);
//等效处理: 专门为Canvas img像素处理运算设计
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
pixels[i] *= gamma;
另外,每个Typed Array类的对象内部都指向一个ArrayBuffer,多个Typed Array对象可以共享同一个ArrayBuffer的缓冲区,我们下面来看一下Typed Array的基本用法:
var b = new ArrayBuffer(8);
var v1 = new Int32Array(b);
var v2 = new Uint8Array(b, 2);
// 创建v3指向b,16位整型,从2字节开,长度为2
var v3 = new Int16Array(b, 2, 2);
以上变量在内存中的存储关系如下:
所以之前的c运算转换为用Typed Array实现如下:
var a = new Int8Array(3)
a[0] = 127
a[1] = a[0] + 1
a[2] = a[0] / 2
第一步要安装支持wasm的浏览器,体验新技术,建议使用激进版浏览器,最新版本中都已经支持了 WebAssembly。除了激进浏览器,在主流版本里开启 flag 也是可以使用 WebAssembly 的:
Chrome: 打开 chrome://flags/#enable-webassembly,选择 enable。
Firefox: 打开 about:config 将 javascript.options.wasm 设置为 true。
大家可以用一下代码试试自己的浏览器是否支持webassembly:
WebAssembly.compile(new Uint8Array([0,97,115,109,1,0,0,0,1,140,128,
128,128,0,2,96,2,127,127,1,127,96,1,127,1,127,3,131,128,128,128,0,2,
0,1,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,
128,128,0,0,7,153,128,128,128,0,3,6,109,101,109,111,114,121,2,0,3,97,100,
100,0,0,6,115,113,117,97,114,101,0,1,10,153,128,128,128,0,2,135,128,128,
128,0,0,32,1,32,0,106,11,135,128,128,128,0,0,32,0,32,0,108,11])).then(module => {
//WebAssembly.Instance 将模块对象转成 WebAssembly 实例
const instance = new WebAssembly.Instance(module)
//通过 instance.exports 可以拿到 wasm 代码输出的接口,剩下的代码就和和普通 javascript 一样了。
const { add, square } = instance.exports
console.log('2 + 4 =', add(2, 4))
console.log('3^2 =', square(3))
console.log('(2 + 5)^2 =', square(add(2 + 5)))
//需要注意数据类型
console.log(square('Tom'))
console.log(add(2e+66, 3e+66))
})
第二步编译工具,wasm.js二进制文件一般都不是手写,而是由C/C++编译转换而来,常用的关键工具就是Emscripten,可以将 C/C++ 编译成 asm.js,使用 WASM 标志也可以直接生成 WebAssembly 二进制文件(后缀是 .wasm)。这里有个在线转换工具可以试试,该工具还可以直接生成二进制文件,和.wast文件(WebAssembly 除了定义了二进制格式以外,还定义了一份对等的文本描述)。上面代码中的二进制码就是以下代码转换的:
int add(int a, int b) {
return a + b;
}
int square(int a) {
return a * a;
}
它转换成的.wast文件如下:
(module
(table 0 anyfunc)
(memory $0 1)
(export "memory" (memory $0))
(export "add" (func $add))
(export "square" (func $square))
(func $add (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $1)
(get_local $0)
)
)
(func $square (; 1 ;) (param $0 i32) (result i32)
(i32.mul
(get_local $0)
(get_local $0)
)
)
)
第三步熟悉使用webassembly的js api,传送门
上面的测试代码就用到了几个常用的js api: WebAssembly.compile 返回 Promise对象,里面的代码是ArrayBuffer二进制。resolve方法的参数module模块对象即为WebAssembly.Module的实例;使用 WebAssembly.Instance 将模块对象转成 WebAssembly 实例(第二个参数可以用来导入变量)。通过 instance.exports 可以拿到 wasm 代码输出的接口,剩下的代码就和和普通 javascript 一样了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。