浏览器最新的 WebAssembly 字节码技术如何?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器最新的 WebAssembly 字节码技术如何?相关的知识,希望对你有一定的参考价值。
最近,WebAssembly 在 Java 圈非常的火!人们都在谈论它多么多么快,怎样怎样改变 Web 开发领域。但是没有人讲他到底为什么那么快。在这篇文章里,我将会帮你了解 WebAssembly 到底为什么那么快。
第一,我们需要知道它到底是什么!WebAssembly 是一种可以使用非 Java 编程语言编写代码并且能在浏览器上运行的技术方案。
当大家谈论起 WebAssembly 时,首先想到的就是 Java。现在,我没有必须在 WebAssembly 和 Java 中选一个的意思。实际上,我们期待开发者在一个项目中把 WebAssembly 和 Java 结合使用。但是,比较这两者是有用的,这对你了解 WebAssembly 有一定帮助。
1. 一点点性能历史
1995 年 Java 诞生。它的设计时间非常短,前十年发展迅速。
紧接着浏览器厂商们就开始了更多的竞争。
2008年,人们称之为浏览器性能大战的时期开始了。很多浏览器加入了即时编译器,又称之为JITs。在这种模式下,Java在运行的时候,JIT 选择模式然后基于这些模式使代码运行更快。
这些 JITs 的引入是浏览器运行代码机制的一个转折点。所有的突然之间,Java 的运行速度快了10倍。
随着这种改进的性能,Java 开始被用于意想不到的事情,比如使用Node.js和Electron构建应用程序。
现在 WebAssembly 可能是的另一个转折点。
在我们没有搞清楚 Java 和 WebAssembly 之间的性能差前,我们需要理解 JS 引擎所做的工作。
2. Java 是如何在浏览器中运行的呢?
作为一个开发人员,您将Java添加到页面时,您有一个目标并遇到一个问题。
目标:你想要告诉计算机做什么
问题:你和计算机使用不通的语言。
您说的是人类的语言,计算机说的是机器语言。尽管你不认为 Java 或者其他高级语言是人类语言,但事实就是这样的。它们的设计是为了让人们认知,不是为机器设计的。
所以Java引擎的工作就是把你的人类语言转化成机器所理解的语言。
在我们没有搞清楚 Java 和 WebAssembly 之间的性能差前,我们需要理解 JS 引擎所做的工作。
2. Java 是如何在浏览器中运行的呢?
作为一个开发人员,您将Java添加到页面时,您有一个目标并遇到一个问题。
目标:你想要告诉计算机做什么
问题:你和计算机使用不通的语言。
您说的是人类的语言,计算机说的是机器语言。尽管你不认为 Java 或者其他高级语言是人类语言,但事实就是这样的。它们的设计是为了让人们认知,不是为机器设计的。
所以Java引擎的工作就是把你的人类语言转化成机器所理解的语言。
在这部电影中,人类语言不能从逐字翻译成外星语言。他们的语言反映出两种对世界不同的认知。人类和机器也是这样。
所以,怎么进行翻译呢?
在编程中,通常有两种翻译方法将代码翻译成机器语言。你可以使用解释器或者编译器。
使用解释器,翻译的过程基本上是一行一行及时生效的。
编译器是另外一种工作方式,它在执行前翻译。
参考技术Ajavascript 从最开始设计,就是一种解释型语言,因为大神觉得让 Javascript 的目标用户- “非专业编程人员和设计师”,了解什么是编译器是一件很残忍的事情。类型自然也是没有的,因为学习类型就要学习 CPU 工作原理, 学习 CPU 工作原理就要学习组成原理, 大神觉得,让 “非专业编程人和设计师” 去了解 1 和 1.0 一个是 CPU 上处理, 一个是 FPU 上面处理这种显而易见的现象是一件很残忍的事情。对象模型是惊人的奇葩,那是因为不想设计得和 Java 一样强大, Netscape 当初想法是主要工作都是 Java 来完成,只有轻量级的简单操作留给 Java script, 做为一种胶水语言( glue langurage). 现在知道为什么叫 Java script了吧? 一个是Java, 一个是和Java 配合的 Script (脚本)。 之前还叫过Live script, 因为脚本和 Java 互动的技术叫 Live connect.对于泛型, 缺省参数,操作符重载, 异常 等等这些黑科技。
好吧,异常后来加上去了。
如果故事到此为止,其实不算一个悲伤故事,大神 10 天时间完成预定目的,东西也发布了,市场反应也不错。
但是问题是,市场反应实在是太好了,好得 Javascript 一路窜红,红得各大浏览器厂商纷纷支持, 成为浏览器里面事实上的官方语言。 在这个过程中, 还顺手干掉了 VB script,
于是这个当初为 “非专业编程人员和设计师” 的解释型语言现在居然变成互联网上面最重要的语言之一,被用来做各种之前想也不敢想的东西,甚至还有人不顾死活的拿他来做WebOS.
于是这个时候,之前所有的小水洼都变成了天坑。之后很长段时间 JS 领域的发展史,都可以说是填坑史。
其中最大的一个坑,就是性能。
参考技术BWeb Assembly 就是第二种方式,说到底,Mozilla, Google, Microsoft, and Apple 觉得 Asm.js 这个方法有前途,想标准化一下,大家都能用。有了大佬们的支持,Web Assembly 比 asm.js 要激进很多。 Web Assembly 连标注 Js 这种事情都懒得做了,不是要 AOT 吗? 我直接给字节码好不好?(后来改成 AST 树)。对于不支持 Web Assembly 的浏览器, 会有一段 Javascript 把 Web Assembly 重新翻译为 Javascript 运行, 这个技术叫 polyfill, html5 刚出来的时候很常用的一个技术。使用 AST 的原因是因为 AST 比字节码更容易压缩,也更容易翻译。
不了解 AST 可以看下面这张图, 说明 Javascript 引擎的执行过程。 Javascript 先编译为 AST, 然后到 Bytecode. AST 的抽象程度比 Bytecode 要高一级。
blazor wasm开发chrome插件
用blazor(Wasm)开发了一个chrome插件感觉效率挺高的,分享给大家
先简单介绍下WebAssembly的原理:
“WebAssembly是一种用于基于堆栈的虚拟机的二进制指令格式”
如上图,浏览器在执行js时是会经历 Parser转成语法树->Compiler转成字节码->JIT即时字节码解释执行
因为WebAssembly 模块已经被编译成一种 JavaScript 字节码形式,现代支持 WebAssembly 的 JavaScript 引擎可以在其 JIT 组件中可以直接解释执行!
mono团队把开源跨平台.NET运行时Mono(也是unity3d的运行时)编译成了WebAssembly ,那么开发的.net程序就可以通过这个运行时在浏览器中加载net程序执行。
近日vs2022发布了,blazor的功能得到进一步提升,
支持AOT将.NET代码直接编译为WebAssembly字节码
支持NativeFileReference添加c语言和rust等原生依赖(手动狗头)
进入正题
开发浏览器插件,常见的就是按照插件的这几块api来进行扩展
右键菜单扩展
Backgroud(可以理解为每个插件都有一个后台一直运行的模块)
popup(浏览器右上角点击插件弹出的窗口模块)
contentScript(嵌入到你想要嵌入的网站内执行)
devtools(开发面板扩展模块)
首先基于这个大佬的模板搭建工程
https://github.com/mingyaulee/Blazor.BrowserExtension
基于模板的话会帮你引入哪些包
我也躺了很多坑,看看我给大佬提的issue,和大佬一起成长
这里我总结一套非常高效的方案给大家:
Backgroud用csharp写
popup,option等的html不要用balzor写,balzor加载html没有任何优势
contentScript用js写,内嵌到网站的,如果是balzor的话会初始化的时候卡1~2s左右,这个会严重影响体验
js和csharp交互
这里把BackGround(csharp开发)作为插件后端 html和js作为插件的前端的方式
右键菜单扩展
在BackGround里面写,包括响应事件
//选中跳转菜单
await WebExtensions.ContextMenus.Create(new WebExtensions.Net.Menus.CreateProperties
{
Title = "测试菜单",
Contexts = new List<ContextType>
{
ContextType.Selection
},
//data是选中的内容包装对象
Onclick = async (data, tab) => { await test(data).ConfigureAwait(false); }
}, EmptyAction);
//非选中跳转菜单
await WebExtensions.ContextMenus.Create(new WebExtensions.Net.Menus.CreateProperties
{
Title = "跳转百度",
Onclick = async (d, tab) => { await OpenUrl("https://www.baidu.com").ConfigureAwait(false); }
}, EmptyAction);
contentScript/popup等
用js写,有2种方式来和Backgroud通讯
1. 事件一来一回的方式
contentScript中发送消息给BackGround
chrome.runtime.sendMessage("消息体", function () { });
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
//处理backgroup发来的消息
});
BackGround注册事件用来接收js发过来的消息
//注册事件接收js过来的消息
await WebExtensions.Runtime.OnMessage.AddListener(OnReceivedCommand);
//处理事件
private bool OnReceivedCommand(object obj, MessageSender sender, Action action){
Console.WriteLine("OnCommand:" + key + $",from TabId:{sender.Tab.Id}");
//处理完成后发送事件给js那边
await WebExtensions.Tabs.SendMessage(sender.Tab.Id.Value, "处理完成了", new SendMessageOptions());
}
2. 长连接方式
js端
var port = chrome.extension.connect({
name: "test"
});
port.onMessage.addListener(function (msg) {
console.log(msg);
});
$('#test').click(e => {
port.postMessage('发消息');
});
csharp端
await WebExtensions.Runtime.OnConnect.AddListener(port =>
{
Console.WriteLine(port.Name + "---》connection");
port.OnMessage.AddListener(new DelegateMethod(async (msg) =>
{
//处理消息
}));
});
目前这种方式有一个需要优化,就是无法在csharp端主动推送消息给js端 给大佬提了issue了,相信很快可以fix https://github.com/mingyaulee/WebExtensions.Net/issues/14
配置/存储相关
有两种方法:
1. chrome.storage.local
这里我封装了一个类专门操作
public class ChromLocalStorage
{
private readonly IWebExtensionsApi _webExtensionsApi;
private readonly IJSRuntime _jsRuntime;
public ChromLocalStorage(IWebExtensionsApi webExtensionsApi, IJSRuntime JsRuntime)
{
_webExtensionsApi = webExtensionsApi;
_jsRuntime = JsRuntime;
}
/// <summary>
/// 调用chrom.storage.local set 把 key 和 value设置进去
/// key返回
/// </summary>
/// <param name="value"></param>
/// <param name="existKey"></param>
/// <returns></returns>
public async Task<string> localSet(string value,string existKey = null)
{
var key = existKey ?? "key_" + DateTime.Now.ToString("yyyyMMddHHmmss");
byte[] bytes = Encoding.UTF8.GetBytes(value);
var encode = Convert.ToBase64String(bytes);
var jss = "var " + key + " = {'" + key + "':'" + encode + "'}";
await _jsRuntime.InvokeVoidAsync("eval", jss);
object data2 = await _jsRuntime.InvokeAsync<object>("eval", key);
await _jsRuntime.InvokeVoidAsync("chrome.storage.local.set", data2);
Console.WriteLine($"call chrome.storage.local.set,key:{key},value:{value},base64Value:{encode}");
return key;
}
public async Task<string> localSet<T>(T value)
{
if (value is string s)
{
return await localSet(s,null);
}
//转成jsonstring
var serialize = JsonSerializer.Serialize(value);
return await localSet(serialize,null);
}
public async Task<T> localGet<T>(string key)
{
var data = await localGet(key);
T deserialize = JsonSerializer.Deserialize<T>(data);
return deserialize;
}
public async Task<string> localGet(string key,bool remove=true)
{
try
{
var local = await _webExtensionsApi.Storage.GetLocal();
var getData = await local.Get(new StorageAreaGetKeys(key));
var data = getData.ToString();
if (string.IsNullOrEmpty(data))
{
return string.Empty;
}
var value = data.Split(new string[] { ":\\"" }, StringSplitOptions.None)[1]
.Split(new string[] { "\\"" }, StringSplitOptions.None)[0];
var str = Convert.FromBase64String(value);
var bastStr = Encoding.UTF8.GetString(str);
//Console.WriteLine($"call chrome.storage.local.get,key:{key},value:{bastStr},base64Value:{value}");
if (remove) await local.Remove(new StorageAreaRemoveKeys(key));
return bastStr;
}
catch (Exception e)
{
return "";
}
}
public async Task localRemove(string key)
{
var local = await _webExtensionsApi.Storage.GetLocal();
await local.Remove(new StorageAreaRemoveKeys(key));
}
}
2. 6.0推出的新技术:采用EFCore + Sqlite
需要用到native的库 https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/Data/e_sqlite3.o
下载下来后放入工程中,然后引入
这里还有一个关键
https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/wwwroot/dbstorage.js
下载这个js后放入工程中,这个js是将sqlite和本地的indexdb进行同步的
//EF的DbContext
public class ClientSideDbContext : DbContext
{
//定义你要存储的表模型
public DbSet<Part> Parts { get; set; } = default!;
public ClientSideDbContext(DbContextOptions<ClientSideDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//设置你的表的索引等
modelBuilder.Entity<Part>().HasIndex(x => x.Id);
modelBuilder.Entity<Part>().HasIndex(x => x.Name);
modelBuilder.Entity<Part>().Property(x => x.Name).UseCollation("nocase");
}
}
//sqlite的初始化以及获取DBContext的方法封装
public class DataSynchronizer
{
public const string SqliteDbFilename = "app.db";
private readonly Task firstTimeSetupTask;
private readonly IDbContextFactory<ClientSideDbContext> dbContextFactory;
public DataSynchronizer(IJSRuntime js, IDbContextFactory<ClientSideDbContext> dbContextFactory)
{
this.dbContextFactory = dbContextFactory;
firstTimeSetupTask = FirstTimeSetupAsync(js);
}
public async Task<ClientSideDbContext> GetPreparedDbContextAsync()
{
await firstTimeSetupTask;
return await dbContextFactory.CreateDbContextAsync();
}
private async Task FirstTimeSetupAsync(IJSRuntime js)
{
//只加载一次 让sqlite和indexdb同步
var module = await js.InvokeAsync<IJSObjectReference>("import", "./js/dbstorage.js");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser")))
{
await module.InvokeVoidAsync("synchronizeFileWithIndexedDb", SqliteDbFilename);
}
using var db = await dbContextFactory.CreateDbContextAsync();
await db.Database.EnsureCreatedAsync();
}
}
在Program.cs进行注册
那么你就可以在Backgroud里面注入并在初始化方法中拿到db上下文
[Inject] public DataSynchronizer DataSynchronizer { get; set; }
//db上下文
private ClientSideDbContext db;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
db = await DataSynchronizer.GetPreparedDbContextAsync();
}
推荐用新的方式,EF写起来更爽更高效,拿到db上下文 就可以很简单的操作插件里面所有用到存储配置等!
这种方式比较适合了解.net生态的人,结合.net的一些库还可以实现很多好玩的功能
excel导出
二维码生成
ajax拦截,转发等
我是正东,开发chrome插件其实很简单,这种方式对于我来说比较高效 哈哈
欢迎白嫖 顺手点个赞吧!
以上是关于浏览器最新的 WebAssembly 字节码技术如何?的主要内容,如果未能解决你的问题,请参考以下文章
安全客简报 | WebAssembly入门:将字节码带入Web世界